Bug 242281 - fuzzy exact value doesn't scale well
Summary: fuzzy exact value doesn't scale well
Status: RESOLVED FIXED
Alias: None
Product: amarok
Classification: Applications
Component: Playlists/Automated Playlist Generator (show other bugs)
Version: 2.3.1-GIT
Platform: Arch Linux Linux
: NOR normal
Target Milestone: 2.3.2
Assignee: Amarok Developers
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-06-20 18:20 UTC by thomas coopman
Modified: 2010-08-15 00:36 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In: 2.3.2


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description thomas coopman 2010-06-20 18:20:22 UTC
Version:           2.3.1-GIT (using KDE 4.4.4) 
OS:                Linux



Reproducible: Didn't try
Comment 1 thomas coopman 2010-06-20 18:27:01 UTC
I noticed that the fuzzy exact slider doesn't always behave predictable. Let me explain, when I chose playlist length equal to 1:30 and set the value of fuzzy-exact to anything but completely fuzzy, the length of the playlist is always very close to 1:30, which is pretty good.  As soon as I set the value to complete fuzzy, the playlist length is not even close to 1:30.

The expected behavior of this should be that for example complete fuzzy is about 20% off of the length, and complete exact about 1 or 2% off. It think this would be better, but this is not a crucial bug of course.

---
Sorry that for the empty first message, but the bug site did weird (or I clicked on submit without knowing it).
Comment 2 Soren Harward 2010-06-20 23:53:13 UTC
I've noticed this too, and I'm working on it.
Comment 3 Soren Harward 2010-08-15 00:30:36 UTC
	A	 src/playlistgenerator/constraints/TagMatchSupport.cpp	 [License: UNKNOWN]

commit 3702226af3aae8a8cda698b5f2fca2286119e0ca
Author: Soren Harward <stharward@gmail.com>
Date:   Fri Jul 16 09:24:07 2010 -0400

    APG: Rewrote fuzzy numerical comparison code
    
    After much testing, I've determined that the fuzzy numerical comparison
    function just gives results that are more like what you'd expect when it
    uses a hard-coded weight for each field, rather than trying to calculate
    a weight on the fly.
    
    BUG: 242281

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f22a61c..ea5d155 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -532,7 +532,7 @@ set(apg_SRCS
     playlistgenerator/constraints/PlaylistLength.cpp
     playlistgenerator/constraints/PreventDuplicates.cpp
     playlistgenerator/constraints/TagMatch.cpp
-    playlistgenerator/constraints/TagMatchFieldsModel.cpp
+    playlistgenerator/constraints/TagMatchSupport.cpp
     playlistgenerator/constraints/TrackSpreader.cpp
 )
 
diff --git a/src/playlistgenerator/Constraint.cpp b/src/playlistgenerator/Constraint.cpp
index ce6783d..40c4737 100644
--- a/src/playlistgenerator/Constraint.cpp
+++ b/src/playlistgenerator/Constraint.cpp
@@ -20,33 +20,4 @@
 
 #include "core/support/Debug.h"
 
-#include <QString>
-
-const double Constraint::magicStrictnessWeight = 3.0;
-
-Constraint::Constraint( ConstraintNode* p ) : ConstraintNode( p ) {}
-
-double Constraint::compare( const QString& a, const int comparison, const QString& b, double strictness ) const
-{
-    Q_UNUSED( strictness ); // strictness is (currently) meaningless for string comparisons
-    if ( comparison == CompareStrEquals ) {
-        if ( a.compare( b, Qt::CaseInsensitive ) == 0 )
-            return 1.0;
-    } else if ( comparison == CompareStrStartsWith ) {
-        if ( a.startsWith( b, Qt::CaseInsensitive ) )
-            return 1.0;
-    } else if ( comparison == CompareStrEndsWith ) {
-        if ( a.endsWith( b, Qt::CaseInsensitive ) )
-            return 1.0;
-    } else if ( comparison == CompareStrContains ) {
-        if ( a.contains( b, Qt::CaseInsensitive ) )
-            return 1.0;
-    } else if ( comparison == CompareStrRegExp ) {
-        QRegExp rx( b );
-        if ( rx.indexIn( a ) >= 0 )
-            return 1.0;
-    } else {
-        return 0.0;
-    }
-    return 0.0;
-}
+Constraint::Constraint( ConstraintNode* p ) : ConstraintNode( p ) {}
\ No newline at end of file
diff --git a/src/playlistgenerator/Constraint.h b/src/playlistgenerator/Constraint.h
index d467a8e..ba28622 100644
--- a/src/playlistgenerator/Constraint.h
+++ b/src/playlistgenerator/Constraint.h
@@ -19,61 +19,16 @@
 
 #include "ConstraintNode.h"
 
-#include "core/meta/Meta.h"
-
-#include <QDomElement>
-#include <QHash>
-#include <QList>
 #include <QObject>
-#include <QString>
-#include <QtGlobal>
-#include <math.h>
 
 /* ABC for all Constraints */
 class Constraint : public ConstraintNode {
     Q_OBJECT
     public:
-        enum NumComparison { CompareNumLessThan, CompareNumEquals, CompareNumGreaterThan };
-        enum StrComparison { CompareStrEquals, CompareStrStartsWith, CompareStrEndsWith, CompareStrContains, CompareStrRegExp };
-        enum DateComparison { CompareDateBefore, CompareDateOn, CompareDateAfter, CompareDateWithin };
-
-        static const double magicStrictnessWeight;
-
         virtual int getNodeType() const { return ConstraintNode::ConstraintType; }
 
     protected:
         Constraint( ConstraintNode* );
-
-        // A couple of helper functions for subclasses
-        double compare(const QString&, const int, const QString&, const double strictness=1.0) const;
-        template <typename T> double compare(const T, const int, const T, const double strictness=1.0) const;
 };
 
-// Templated function from Constraint
-template <typename T> double Constraint::compare( const T a, const int comparison, const T b, const double strictness ) const {
-
-    /* There's no mathematical rigor to this factor; I came up with some test
-     * cases and what answers I expected, and tried a bunch of different
-     * factors.  This one behaved closest to what I wanted.  -- sth */
-
-    double factor = ( exp( magicStrictnessWeight * strictness) / ( sqrt( (double) b ) + 1.0 ) );
-    if ( comparison == CompareNumEquals ) {
-        // fuzzy equals -- within 1%
-        if ( qAbs( a - b ) < ( ( a + b ) / 200.0 ) ) {
-            return 1.0;
-        } else if ( a > b ) {
-            return exp( factor * ( b - a ) );
-        } else {
-            return exp( factor * ( a - b ) );
-        }
-    } else if ( comparison == CompareNumGreaterThan ) {
-        return (a > b) ? 1.0 : exp( factor * ( a - ( b * 1.05 ) ) );
-    } else if ( comparison == CompareNumLessThan ) {
-        return (a < b) ? 1.0 : exp( factor * ( b - ( a * 1.05 ) ) );
-    } else {
-        return 0.0;
-    }
-    return 0.0;
-}
-
 #endif
diff --git a/src/playlistgenerator/TODO b/src/playlistgenerator/TODO
index 8e30fa5..1727bb1 100644
--- a/src/playlistgenerator/TODO
+++ b/src/playlistgenerator/TODO
@@ -8,8 +8,7 @@ Backend:
 Constraints:
 - fix preventduplicates: the delta functions are kinda broken
 - last.fm or echonest similar artists constraint
-- proper handling of empty fingerprints in SimilarityChain
+- playlist length (ie, # of tracks) constraint
 
 GUI:
-- "simple" presets?
-- context menu for APGCategory
+- context menu for APGCategory
\ No newline at end of file
diff --git a/src/playlistgenerator/constraints/Checkpoint.cpp b/src/playlistgenerator/constraints/Checkpoint.cpp
index eb30407..57237e9 100644
--- a/src/playlistgenerator/constraints/Checkpoint.cpp
+++ b/src/playlistgenerator/constraints/Checkpoint.cpp
@@ -32,6 +32,7 @@
 #include <QtGlobal>
 
 #include <climits>
+#include <math.h>
 
 Constraint*
 ConstraintTypes::Checkpoint::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
diff --git a/src/playlistgenerator/constraints/PlaylistLength.cpp b/src/playlistgenerator/constraints/PlaylistLength.cpp
index c5df1e3..76b4967 100644
--- a/src/playlistgenerator/constraints/PlaylistLength.cpp
+++ b/src/playlistgenerator/constraints/PlaylistLength.cpp
@@ -29,6 +29,7 @@
 #include <QtGlobal>
 
 #include <stdlib.h>
+#include <math.h>
 
 Constraint*
 ConstraintTypes::PlaylistLength::createFromXml( QDomElement& xmlelem, ConstraintNode* p )
@@ -80,7 +81,7 @@ ConstraintTypes::PlaylistLength::PlaylistLength( QDomElement& xmlelem, Constrain
 ConstraintTypes::PlaylistLength::PlaylistLength( ConstraintNode* p )
         : Constraint( p )
         , m_length( 0 )
-        , m_comparison( Constraint::CompareNumEquals )
+        , m_comparison( CompareNumEquals )
         , m_strictness( 1.0 )
 {
 }
@@ -187,9 +188,9 @@ ConstraintTypes::PlaylistLength::swapTracks( const Meta::TrackList&, const int,
 int
 ConstraintTypes::PlaylistLength::suggestInitialPlaylistSize() const
 {
-    if ( m_comparison == Constraint::CompareNumLessThan ) {
+    if ( m_comparison == CompareNumLessThan ) {
         return m_length / 300000;
-    } else if ( m_comparison == Constraint::CompareNumGreaterThan ) {
+    } else if ( m_comparison == CompareNumGreaterThan ) {
         return m_length / 180000;
     } else {
         return m_length / 240000;
@@ -201,7 +202,7 @@ ConstraintTypes::PlaylistLength::vote( const Meta::TrackList& playlist, const Me
 {
     ConstraintNode::Vote* v = 0;
 
-    if ( m_comparison == Constraint::CompareNumLessThan ) {
+    if ( m_comparison == CompareNumLessThan ) {
         if ( m_totalLength > m_length) {
             int longestLength = 0;
             int longestPosition = -1;
@@ -217,14 +218,14 @@ ConstraintTypes::PlaylistLength::vote( const Meta::TrackList& playlist, const Me
             v->operation = ConstraintNode::OperationDelete;
             v->place = longestPosition;
         }
-    } else if ( m_comparison == Constraint::CompareNumGreaterThan ) {
+    } else if ( m_comparison == CompareNumGreaterThan ) {
         if ( m_totalLength < m_length) {
             v = new ConstraintNode::Vote;
             v->operation = ConstraintNode::OperationInsert;
             v->place = KRandom::random() % ( playlist.size() + 1 );
             v->track = domain.at( KRandom::random() % domain.size() );
         }
-    } else if ( m_comparison == Constraint::CompareNumEquals ) {
+    } else if ( m_comparison == CompareNumEquals ) {
         int deviation = qAbs( m_totalLength - m_length );
         if ( m_totalLength > m_length ) {
             int randomIdx = KRandom::random() % playlist.size();
@@ -250,11 +251,11 @@ ConstraintTypes::PlaylistLength::vote( const Meta::TrackList& playlist, const Me
 QString
 ConstraintTypes::PlaylistLength::comparisonToString() const
 {
-    if ( m_comparison == Constraint::CompareNumEquals ) {
+    if ( m_comparison == CompareNumEquals ) {
         return QString( i18nc("duration of playlist equals some time", "equals") );
-    } else if ( m_comparison == Constraint::CompareNumGreaterThan ) {
+    } else if ( m_comparison == CompareNumGreaterThan ) {
         return QString( i18n("longer than") );
-    } else if ( m_comparison == Constraint::CompareNumLessThan ) {
+    } else if ( m_comparison == CompareNumLessThan ) {
         return QString( i18n("shorter than") );
     } else {
         return QString( i18n("unknown comparison") );
@@ -265,11 +266,11 @@ double
 ConstraintTypes::PlaylistLength::transformLength( const qint64 l ) const
 {
     double factor = m_strictness * 0.0003;
-    if ( m_comparison == Constraint::CompareNumEquals ) {
+    if ( m_comparison == CompareNumEquals ) {
         return 4.0 / ( ( 1.0 + exp( factor*( double )( l - m_length ) ) )*( 1.0 + exp( factor*( double )( m_length - l ) ) ) );
-    } else if ( m_comparison == Constraint::CompareNumLessThan ) {
+    } else if ( m_comparison == CompareNumLessThan ) {
         return 1.0 / ( 1.0 + exp( factor*( double )( l - m_length ) ) );
-    } else if ( m_comparison == Constraint::CompareNumGreaterThan ) {
+    } else if ( m_comparison == CompareNumGreaterThan ) {
         return 1.0 / ( 1.0 + exp( factor*( double )( m_length - l ) ) );
     }
     return 1.0;
diff --git a/src/playlistgenerator/constraints/PlaylistLength.h b/src/playlistgenerator/constraints/PlaylistLength.h
index 4a87a5a..d21ac71 100644
--- a/src/playlistgenerator/constraints/PlaylistLength.h
+++ b/src/playlistgenerator/constraints/PlaylistLength.h
@@ -38,6 +38,8 @@ namespace ConstraintTypes {
     class PlaylistLength : public Constraint {
         Q_OBJECT
 
+        enum NumComparison { CompareNumLessThan, CompareNumEquals, CompareNumGreaterThan };
+
         public:
             static Constraint* createFromXml(QDomElement&, ConstraintNode*);
             static Constraint* createNew(ConstraintNode*);
@@ -47,7 +49,7 @@ namespace ConstraintTypes {
             virtual void toXml(QDomDocument&, QDomElement&) const;
 
             virtual QString getName() const;
-            
+
             virtual Collections::QueryMaker* initQueryMaker(Collections::QueryMaker*) const;
             virtual double satisfaction(const Meta::TrackList&);
             virtual double deltaS_insert(const Meta::TrackList&, const Meta::TrackPtr, const int) const;
diff --git a/src/playlistgenerator/constraints/TagMatch.cpp b/src/playlistgenerator/constraints/TagMatch.cpp
index 22a2cf5..29c9933 100644
--- a/src/playlistgenerator/constraints/TagMatch.cpp
+++ b/src/playlistgenerator/constraints/TagMatch.cpp
@@ -60,6 +60,7 @@ ConstraintTypes::TagMatch::registerMe()
 
 ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p )
         : MatchingConstraint( p )
+        , m_comparer( new Comparer() )
         , m_fieldsModel( new TagMatchFieldsModel() )
 {
     QDomAttr a;
@@ -82,7 +83,7 @@ ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p )
         if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
             m_value = a.value().toInt();
         } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
-            if ( m_comparison == Constraint::CompareDateWithin ) {
+            if ( m_comparison == CompareDateWithin ) {
                 QStringList parts = a.value().split(" ");
                 if ( parts.size() == 2 ) {
                     int u = parts.at( 0 ).toInt();
@@ -114,17 +115,19 @@ ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p )
 
 ConstraintTypes::TagMatch::TagMatch( ConstraintNode* p )
         : MatchingConstraint( p )
-        , m_comparison( Constraint::CompareStrEquals )
+        , m_comparison( CompareStrEquals )
         , m_field( "title" )
         , m_invert( false )
         , m_strictness( 1.0 )
         , m_value()
+        , m_comparer( new Comparer() )
         , m_fieldsModel( new TagMatchFieldsModel() )
 {
 }
 
 ConstraintTypes::TagMatch::~TagMatch()
 {
+    delete m_comparer;
     delete m_fieldsModel;
 }
 
@@ -192,47 +195,43 @@ ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const
 {
     if ( ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) ) {
         int v = m_value.toInt();
-
-        double factor;
-        int range;
-        if ( m_field != "length" ) {
-            // compute fuzzy ranges -- this marks the boundary beyond which the fuzzy match probability is less than 1%
-            factor = exp( Constraint::magicStrictnessWeight * m_strictness ) / ( sqrt(( double )v ) + 1.0 ); // duplicated from Constraint::compare()
-            range = (int)ceil( 4.6051702 / factor );
-        } else {
-            // small kludge to get fuzziness to play better in the case of track lengths
-            factor = exp( Constraint::magicStrictnessWeight * m_strictness ) / ( sqrt(( double )v/1000.0 ) + 1.0 );
-            range = (int)ceil( 4605.1702 / factor );
-        }
-        if ( m_comparison == Constraint::CompareNumEquals ) {
+        int range = static_cast<int>( m_comparer->rangeNum( m_strictness, m_fieldsModel->meta_value_of( m_field ) ) );
+        if ( m_comparison == CompareNumEquals ) {
             if ( !m_invert ) {
-                qm->beginAnd();
-                qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
-                qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
-                qm->endAndOr();
+                if ( m_strictness < 0.99 ) { // fuzzy approximation of "1.0"
+                    qm->beginAnd();
+                    qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
+                    qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
+                    qm->endAndOr();
+                } else {
+                    qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
+                }
+            } else {
+                if ( m_strictness > 0.99 ) {
+                    qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals );
+                }
             }
-        } else if ( m_comparison == Constraint::CompareNumGreaterThan ) {
+        } else if ( m_comparison == CompareNumGreaterThan ) {
             if ( m_invert )
                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::GreaterThan );
             else
                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan );
-        } else if ( m_comparison == Constraint::CompareNumLessThan ) {
+        } else if ( m_comparison == CompareNumLessThan ) {
             if ( m_invert )
                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::LessThan );
             else
                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan );
         }
     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
-        double factor = ( exp( 5.0 * m_strictness ) ) / 1e6; // duplicated from this::dateComparison()
-        uint range = (uint)ceil( 4.6051702 / factor );
         uint referenceDate = 0;
-        if ( m_comparison == Constraint::CompareDateBefore ) {
+        int range = m_comparer->rangeDate( m_strictness );
+        if ( m_comparison == CompareDateBefore ) {
             referenceDate = m_value.toDateTime().toTime_t();
             if ( m_invert )
                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::LessThan );
             else
                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
-        } else if ( m_comparison == Constraint::CompareDateOn ) {
+        } else if ( m_comparison == CompareDateOn ) {
             referenceDate = m_value.toDateTime().toTime_t();
             if ( !m_invert ) {
                 qm->beginAnd();
@@ -240,13 +239,13 @@ ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const
                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan );
                 qm->endAndOr();
             }
-        } else if ( m_comparison == Constraint::CompareDateAfter ) {
+        } else if ( m_comparison == CompareDateAfter ) {
             referenceDate = m_value.toDateTime().toTime_t();
             if ( m_invert )
                 qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan );
             else
                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
-        } else if ( m_comparison == Constraint::CompareDateWithin ) {
+        } else if ( m_comparison == CompareDateWithin ) {
             QDateTime now = QDateTime::currentDateTime();
             DateRange r = m_value.value<DateRange>();
             switch ( r.second ) {
@@ -268,22 +267,22 @@ ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const
                 qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan );
         }
     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) {
-        if ( m_comparison == Constraint::CompareStrEquals ) {
+        if ( m_comparison == CompareStrEquals ) {
             if ( m_invert )
                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
             else
                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true );
-        } else if ( m_comparison == Constraint::CompareStrStartsWith ) {
+        } else if ( m_comparison == CompareStrStartsWith ) {
             if ( m_invert )
                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
             else
                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false );
-        } else if ( m_comparison == Constraint::CompareStrEndsWith ) {
+        } else if ( m_comparison == CompareStrEndsWith ) {
             if ( m_invert )
                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
             else
                 qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true );
-        } else if ( m_comparison == Constraint::CompareStrContains ) {
+        } else if ( m_comparison == CompareStrContains ) {
             if ( m_invert )
                 qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false );
             else
@@ -405,6 +404,13 @@ ConstraintTypes::TagMatch::vote( const Meta::TrackList& playlist, const Meta::Tr
     return 0;
 }
 
+void
+ConstraintTypes::TagMatch::audit( const Meta::TrackList& tl ) const
+{
+    foreach( const Meta::TrackPtr t, tl ) {
+        debug() << t->prettyName() << matches( t );
+    }
+}
 
 const QBitArray
 ConstraintTypes::TagMatch::whatTracksMatch( const Meta::TrackList& tl )
@@ -423,114 +429,52 @@ ConstraintTypes::TagMatch::constraintMatchType() const
     return ( 0 << 28 ) + m_fieldsModel->index_of( m_field );
 }
 
-double
-ConstraintTypes::TagMatch::dateComparison( uint trackDate ) const
-{
-    /* comparing dates is a little bit tricky, so I split it off into its own function */
-    int comp;
-    uint referenceDate = 0;
-    if ( m_comparison == Constraint::CompareDateWithin ) {
-        comp = Constraint::CompareDateAfter;
-        QDateTime now = QDateTime::currentDateTime();
-        DateRange r = m_value.value<DateRange>();
-        switch ( r.second ) {
-            case 0:
-                referenceDate = now.addDays( -1 * r.first ).toTime_t();
-                break;
-            case 1:
-                referenceDate = now.addMonths( -1 * r.first ).toTime_t();
-                break;
-            case 2:
-                referenceDate = now.addYears( -1 * r.first ).toTime_t();
-                break;
-            default:
-                break;
-        }
-    } else {
-        comp = m_comparison;
-        referenceDate = m_value.toDateTime().toTime_t();
-    }
-
-    /* I decided to keep the comparison logic here instead of passing it down
-     * to Constraint::compare() because ::compare() calculates the strictness
-     * factor in proportion to the values that were passed to it.  The numbers
-     * involved in date calculations (ie, seconds since the epoch) are so large
-     * that they make ::compare()'s factor pretty much useless, plus the
-     * strictness factor gets smaller when later dates are chosen.  This is a
-     * more useful strictness factor for date calculations. -- stharward */
-
-    double r = 0.0;
-    double factor = ( exp( 5.0 * m_strictness ) ) / 1e6;
-    if ( comp == Constraint::CompareDateOn ) {
-        // fuzzy equals -- within 18 hours
-        if ( qAbs( (double)trackDate - (double)referenceDate ) < ( 64800.0 ) )
-            r = 1.0;
-        else if ( trackDate > referenceDate )
-            r = exp( factor * ( (double)referenceDate - (double)trackDate ) );
-        else
-            r = exp( factor * ( (double)trackDate - (double)referenceDate ) );
-    } else if ( comp == Constraint::CompareDateAfter ) {
-        r = ( trackDate > referenceDate ) ? 1.0 : exp( factor * ( (double)trackDate - (double)referenceDate ) );
-    } else if ( comp == Constraint::CompareDateBefore ) {
-        r = ( trackDate < referenceDate ) ? 1.0 : exp( factor * ( (double)referenceDate - (double)trackDate ) );
-    } else {
-        r = 0.0;
-    }
-
-    return r;
-}
-
-double
-ConstraintTypes::TagMatch::labelComparison( Meta::TrackPtr t ) const
-{
-    Meta::LabelList labelList = t->labels();
-
-    double v = 0.0;
-    foreach ( Meta::LabelPtr label, labelList ) {
-        // this is correct ...
-        // v = qMax( compare( label, m_comparison, m_value.toString() ), v );
-
-        // ... but as long as compare() returns only 0.0 or 1.0, the following is faster:
-        v = compare( label->prettyName(), m_comparison, m_value.toString() );
-        if ( v == 1.0 ) {
-            return 1.0;
-        }
-    }
-
-    return v;
-}
 
 QString
 ConstraintTypes::TagMatch::comparisonToString() const
 {
     if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) {
-        if ( m_comparison == Constraint::CompareNumEquals ) {
+        if ( m_comparison == CompareNumEquals ) {
             return QString( i18nc("a numerical tag (like year or track number) equals a value","equals") );
-        } else if ( m_comparison == Constraint::CompareNumGreaterThan ) {
+        } else if ( m_comparison == CompareNumGreaterThan ) {
             return QString( i18n("greater than") );
-        } else if ( m_comparison == Constraint::CompareNumLessThan ) {
+        } else if ( m_comparison == CompareNumLessThan ) {
             return QString( i18n("less than") );
         }
     } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
-        if ( m_comparison == Constraint::CompareDateBefore ) {
+        if ( m_comparison == CompareDateBefore ) {
             return QString( i18n("before") );
-        } else if ( m_comparison == Constraint::CompareDateOn ) {
+        } else if ( m_comparison == CompareDateOn ) {
             return QString( i18n("on") );
-        } else if ( m_comparison == Constraint::CompareDateAfter ) {
+        } else if ( m_comparison == CompareDateAfter ) {
             return QString( i18n("after") );
-        } else if ( m_comparison == Constraint::CompareDateWithin ) {
+        } else if ( m_comparison == CompareDateWithin ) {
             return QString( i18n("within") );
         }
     } else {
-        if ( m_comparison == Constraint::CompareStrEquals ) {
+#if 0
+        // FIXME: Replace the block below with this one after string freeze is lifted
+        if ( m_comparison == CompareStrEquals ) {
+            return QString( i18nc("an alphabetical tag (like title or artist name) equals some string","equals") );
+        } else if ( m_comparison == CompareStrStartsWith ) {
+            return QString( i18nc("an alphabetical tag (like title or artist name) starts with some string","starts with") );
+        } else if ( m_comparison == CompareStrEndsWith ) {
+            return QString( i18nc("an alphabetical tag (like title or artist name) ends with some string","ends with") );
+        } else if ( m_comparison == CompareStrContains ) {
+            return QString( i18nc("an alphabetical tag (like title or artist name) contains some string","contains") );
+        } else if ( m_comparison == CompareStrRegExp ) {
+            return QString( i18n("regexp") );
+        }
+#endif
+        if ( m_comparison == CompareStrEquals ) {
             return QString( i18nc("an alphabetical tag (like title or artist name) equals some string","equals") );
-        } else if ( m_comparison == Constraint::CompareStrStartsWith ) {
+        } else if ( m_comparison == CompareStrStartsWith ) {
             return QString( i18n("starts with") );
-        } else if ( m_comparison == Constraint::CompareStrEndsWith ) {
+        } else if ( m_comparison == CompareStrEndsWith ) {
             return QString( i18n("ends with") );
-        } else if ( m_comparison == Constraint::CompareStrContains ) {
+        } else if ( m_comparison == CompareStrContains ) {
             return QString( i18n("contains") );
-        } else if ( m_comparison == Constraint::CompareStrRegExp ) {
+        } else if ( m_comparison == CompareStrRegExp ) {
             return QString( i18n("regexp") );
         }
     }
@@ -541,7 +485,7 @@ QString
 ConstraintTypes::TagMatch::valueToString() const
 {
     if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) {
-        if ( m_comparison != Constraint::CompareDateWithin ) {
+        if ( m_comparison != CompareDateWithin ) {
             return m_value.toDate().toString( Qt::ISODate );
         } else {
             QString unit;
@@ -570,70 +514,67 @@ ConstraintTypes::TagMatch::matches( Meta::TrackPtr track ) const
 {
     if ( !m_matchCache.contains( track ) ) {
         double v = 0.0;
-        int lengthInSec, targetInSec; // these are used for track length calculations below
-        switch ( m_fieldsModel->meta_value_of( m_field ) ) {
+        qint64 fmv = m_fieldsModel->meta_value_of( m_field );
+        switch ( fmv ) {
             case Meta::valUrl:
-                v = compare( track->prettyUrl(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->prettyUrl(), m_comparison, m_value.toString() );
                 break;
             case Meta::valTitle:
-                v = compare( track->prettyName(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->prettyName(), m_comparison, m_value.toString() );
                 break;
             case Meta::valArtist:
-                v = compare( track->artist()->prettyName(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->artist()->prettyName(), m_comparison, m_value.toString() );
                 break;
             case Meta::valAlbum:
-                v = compare( track->album()->prettyName(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->album()->prettyName(), m_comparison, m_value.toString() );
                 break;
             case Meta::valGenre:
-                v = compare( track->genre()->prettyName(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->genre()->prettyName(), m_comparison, m_value.toString() );
                 break;
             case Meta::valComposer:
-                v = compare( track->composer()->prettyName(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->composer()->prettyName(), m_comparison, m_value.toString() );
                 break;
             case Meta::valYear:
-                v = compare<int>( track->year()->prettyName().toInt(), m_comparison, m_value.toInt() );
+                v = m_comparer->compareNum( track->year()->prettyName().toInt(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valComment:
-                v = compare( track->comment(), m_comparison, m_value.toString() );
+                v = m_comparer->compareStr( track->comment(), m_comparison, m_value.toString() );
                 break;
             case Meta::valTrackNr:
-                v = compare<int>( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness );
+                v = m_comparer->compareNum( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valDiscNr:
-                v = compare<int>( track->discNumber(), m_comparison, m_value.toInt(), m_strictness );
+                v = m_comparer->compareNum( track->discNumber(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valLength:
-                // the strictness factor doesn't handle milliseconds very well
-                lengthInSec = track->length() / 1000;
-                targetInSec = m_value.toInt() / 1000;
-                v = compare<int>( lengthInSec, m_comparison, targetInSec, m_strictness );
+                v = m_comparer->compareNum( track->length(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valBitrate:
-                v = compare<int>( track->bitrate(), m_comparison, m_value.toInt(), m_strictness );
+                v = m_comparer->compareNum( track->bitrate(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valFilesize:
-                v = compare<int>( track->filesize(), m_comparison, m_value.toInt(), m_strictness );
+                v = m_comparer->compareNum( track->filesize(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valCreateDate:
-                v = dateComparison( track->createDate().toTime_t() );
+                v = m_comparer->compareDate( track->createDate().toTime_t(), m_comparison, m_value, m_strictness );
                 break;
             case Meta::valScore:
-                v = compare<double>( track->score(), m_comparison, m_value.toDouble(), m_strictness );
+                v = m_comparer->compareNum( track->score(), m_comparison, m_value.toDouble(), m_strictness, fmv );
                 break;
             case Meta::valRating:
-                v = compare<int>( track->rating(), m_comparison, m_value.toInt(), m_strictness );
+                v = m_comparer->compareNum( track->rating(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valFirstPlayed:
-                v = dateComparison( track->firstPlayed() );
+                v = m_comparer->compareDate( track->firstPlayed(), m_comparison, m_value, m_strictness );
                 break;
             case Meta::valLastPlayed:
-                v = dateComparison( track->lastPlayed() );
+                v = m_comparer->compareDate( track->lastPlayed(), m_comparison, m_value, m_strictness );
                 break;
             case Meta::valPlaycount:
-                v = compare<int>( track->playCount(), m_comparison, m_value.toInt(), m_strictness );
+                v = m_comparer->compareNum( track->playCount(), m_comparison, m_value.toInt(), m_strictness, fmv );
                 break;
             case Meta::valLabel:
-                v = labelComparison( track );
+                v = m_comparer->compareLabels( track, m_comparison, m_value.toString() );
                 break;
             default:
                 v = 0.0;
@@ -733,7 +674,7 @@ ConstraintTypes::TagMatchEditWidget::TagMatchEditWidget(
     } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) {
         ui.comboBox_ComparisonDate->setCurrentIndex( comparison );
         ui.slider_StrictnessDate->setValue( strictness );
-        if ( comparison == Constraint::CompareDateWithin ) {
+        if ( comparison == TagMatch::CompareDateWithin ) {
             ui.stackedWidget_Date->setCurrentIndex( 1 );
             ui.spinBox_ValueDateValue->setValue( value.value<DateRange>().first );
             ui.comboBox_ValueDateUnit->setCurrentIndex( value.value<DateRange>().second );
@@ -759,7 +700,7 @@ ConstraintTypes::TagMatchEditWidget::~TagMatchEditWidget()
 void
 ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonDate_currentIndexChanged( int c )
 {
-    if ( c == Constraint::CompareDateWithin )
+    if ( c == TagMatch::CompareDateWithin )
         ui.stackedWidget_Date->setCurrentIndex( 1 );
     else
         ui.stackedWidget_Date->setCurrentIndex( 0 );
@@ -818,7 +759,7 @@ ConstraintTypes::TagMatchEditWidget::on_comboBox_Field_currentIndexChanged( int
             ui.stackedWidget_Field->setCurrentIndex( 1 );
             c = ui.comboBox_ComparisonDate->currentIndex();
             s = ui.slider_StrictnessDate->value();
-            if ( c == Constraint::CompareDateWithin ) {
+            if ( c == TagMatch::CompareDateWithin ) {
                 ui.stackedWidget_Date->setCurrentIndex( 1 );
                 int a = ui.spinBox_ValueDateValue->value();
                 int b = ui.comboBox_ValueDateUnit->currentIndex();
diff --git a/src/playlistgenerator/constraints/TagMatch.h b/src/playlistgenerator/constraints/TagMatch.h
index dfa2c35..094a68b 100644
--- a/src/playlistgenerator/constraints/TagMatch.h
+++ b/src/playlistgenerator/constraints/TagMatch.h
@@ -49,8 +49,14 @@ namespace ConstraintTypes {
     class TagMatch : public MatchingConstraint {
         Q_OBJECT
 
+        /* support classes declared below */
+        class Comparer;
+
         public:
             enum FieldTypes { FieldTypeInt, FieldTypeDate, FieldTypeString };
+            enum NumComparison { CompareNumLessThan, CompareNumEquals, CompareNumGreaterThan };
+            enum StrComparison { CompareStrEquals, CompareStrStartsWith, CompareStrEndsWith, CompareStrContains, CompareStrRegExp };
+            enum DateComparison { CompareDateBefore, CompareDateOn, CompareDateAfter, CompareDateWithin };
 
             static Constraint* createFromXml(QDomElement&, ConstraintNode*);
             static Constraint* createNew(ConstraintNode*);
@@ -74,6 +80,10 @@ namespace ConstraintTypes {
 
             virtual ConstraintNode::Vote* vote( const Meta::TrackList&, const Meta::TrackList& ) const;
 
+#ifndef KDE_NO_DEBUG_OUTPUT
+            virtual void audit( const Meta::TrackList& ) const;
+#endif
+
             // Implementation of MatchingConstraint virtuals
             const QBitArray whatTracksMatch( const Meta::TrackList& );
             int constraintMatchType() const;
@@ -97,20 +107,69 @@ namespace ConstraintTypes {
             double m_strictness;
             QVariant m_value;
 
-            // convenience class
+            // convenience classes
+            const Comparer* const m_comparer;
             const TagMatchFieldsModel* const m_fieldsModel;
 
             // internal state data
             double m_satisfaction;
 
             // convenience functions
-            double dateComparison( uint ) const;
-            double labelComparison( Meta::TrackPtr ) const;
             QString comparisonToString() const;
             QString valueToString() const;
 
             bool matches( const Meta::TrackPtr ) const; // match values are fuzzily calculated
             mutable QHash<Meta::TrackPtr, bool> m_matchCache; // internal cache for per-track true/false data
+
+
+        /* support class that does fuzzy comparisons */
+        class Comparer {
+            public:
+                Comparer();
+                ~Comparer();
+
+                double compareNum( const double, const int, const double, const double, const qint64 ) const;
+                double compareStr( const QString&, const int, const QString& ) const;
+                double compareDate( const uint, const int, const QVariant&, const double ) const;
+                double compareLabels( const Meta::TrackPtr, const int, const QString& ) const;
+
+                // rough inverses of the comparison
+                uint rangeDate( const double ) const;
+                int rangeNum( const double, const qint64 ) const;
+
+            private:
+                QHash<qint64, double> m_numFieldWeight;
+                const double m_dateWeight;
+
+                double fuzzyProb( const double, const double, const double, const double ) const;
+        };
+
+    };
+
+    /* support class that manages data relationships for the various fields */
+    class TagMatchFieldsModel : public QAbstractListModel {
+        Q_OBJECT
+
+        public:
+            TagMatchFieldsModel();
+            ~TagMatchFieldsModel();
+
+            // required by QAbstractListModel
+            QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const;
+            int rowCount( const QModelIndex& parent = QModelIndex() ) const;
+
+            bool contains( const QString& ) const;
+            int index_of( const QString& ) const;
+            QString field_at( int ) const;
+            qint64 meta_value_of( const QString& ) const;
+            QString pretty_name_of( const QString& ) const;
+            TagMatch::FieldTypes type_of( const QString& ) const;
+
+        private:
+            QList<QString> m_fieldNames;
+            QHash<QString, TagMatch::FieldTypes> m_fieldTypes;
+            QHash<QString, qint64> m_fieldMetaValues;
+            QHash<QString, QString> m_fieldPrettyNames;
     };
 
     class TagMatchEditWidget : public QWidget {
@@ -156,38 +215,13 @@ namespace ConstraintTypes {
             void on_lineEdit_StringValue_textChanged( const QString& );
             void on_rating_RatingValue_ratingChanged( int );
             void on_timeEdit_TimeValue_timeChanged( const QTime& );
-            void slotUpdateComboBoxLabels( int );            
+            void slotUpdateComboBoxLabels( int );
 
         private:
             Ui::TagMatchEditWidget ui;
             TagMatchFieldsModel* const m_fieldsModel;
     };
 
-    class TagMatchFieldsModel : public QAbstractListModel {
-        Q_OBJECT
-
-        public:
-            TagMatchFieldsModel();
-            ~TagMatchFieldsModel();
-
-            // required by QAbstractListModel
-            QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const;
-            int rowCount( const QModelIndex& parent = QModelIndex() ) const;
-
-            bool contains( const QString& ) const;
-            int index_of( const QString& ) const;
-            QString field_at( int ) const;
-            qint64 meta_value_of( const QString& ) const;
-            QString pretty_name_of( const QString& ) const;
-            TagMatch::FieldTypes type_of( const QString& ) const;
-
-        private:
-            QList<QString> m_fieldNames;
-            QHash<QString, TagMatch::FieldTypes> m_fieldTypes;
-            QHash<QString, qint64> m_fieldMetaValues;
-            QHash<QString, QString> m_fieldPrettyNames;
-    };
-
 } // namespace ConstraintTypes
 
 typedef QPair<int,int> DateRange;
diff --git a/src/playlistgenerator/constraints/TagMatchEditWidget.ui b/src/playlistgenerator/constraints/TagMatchEditWidget.ui
index 06d0fe6..2f7fc0a 100644
--- a/src/playlistgenerator/constraints/TagMatchEditWidget.ui
+++ b/src/playlistgenerator/constraints/TagMatchEditWidget.ui
@@ -306,7 +306,11 @@ p, li { white-space: pre-wrap; }
                   <item>
                    <layout class="QHBoxLayout" name="horizontalLayout_6">
                     <item>
-                     <widget class="KIntSpinBox" name="spinBox_ValueDateValue"/>
+                     <widget class="KIntSpinBox" name="spinBox_ValueDateValue">
+                      <property name="maximum">
+                       <number>999</number>
+                      </property>
+                     </widget>
                     </item>
                     <item>
                      <widget class="QComboBox" name="comboBox_ValueDateUnit"/>
diff --git a/src/playlistgenerator/constraints/TagMatchFieldsModel.cpp b/src/playlistgenerator/constraints/TagMatchFieldsModel.cpp
deleted file mode 100644
index fd3cf74..0000000
--- a/src/playlistgenerator/constraints/TagMatchFieldsModel.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/****************************************************************************************
- * Copyright (c) 2010 Soren Harward <stharward@gmail.com>                               *
- *                                                                                      *
- * This program is free software; you can redistribute it and/or modify it under        *
- * the terms of the GNU General Public License as published by the Free Software        *
- * Foundation; either version 2 of the License, or (at your option) any later           *
- * version.                                                                             *
- *                                                                                      *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
- *                                                                                      *
- * You should have received a copy of the GNU General Public License along with         *
- * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
- ****************************************************************************************/
-
-#include "TagMatch.h"
-
-#include "core/meta/support/MetaConstants.h"
-
-ConstraintTypes::TagMatchFieldsModel::TagMatchFieldsModel()
-{
-    // if you add something here, you need to update TagMatch::matches() to handle it
-    m_fieldNames << "url"
-                 << "title"
-                 << "artist name"
-                 << "album name"
-                 << "genre"
-                 << "composer"
-                 << "year"
-                 << "comment"
-                 << "track number"
-                 << "disc number"
-                 << "length"
-                 << "score"
-                 << "rating"
-                 << "create date"
-                 << "first played"
-                 << "last played"
-                 << "play count"
-                 << "label";
-
-    m_fieldTypes.insert( "url", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "title", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "artist name", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "album name", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "genre", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "composer", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "year", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "comment", TagMatch::FieldTypeString );
-    m_fieldTypes.insert( "track number", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "disc number", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "length", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "create date", TagMatch::FieldTypeDate);
-    m_fieldTypes.insert( "score", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "rating", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "first played", TagMatch::FieldTypeDate );
-    m_fieldTypes.insert( "last played", TagMatch::FieldTypeDate );
-    m_fieldTypes.insert( "play count", TagMatch::FieldTypeInt );
-    m_fieldTypes.insert( "label", TagMatch::FieldTypeString );
-
-    m_fieldMetaValues.insert( "url", Meta::valUrl );
-    m_fieldMetaValues.insert( "title", Meta::valTitle );
-    m_fieldMetaValues.insert( "artist name", Meta::valArtist );
-    m_fieldMetaValues.insert( "album name", Meta::valAlbum );
-    m_fieldMetaValues.insert( "genre", Meta::valGenre );
-    m_fieldMetaValues.insert( "composer", Meta::valComposer );
-    m_fieldMetaValues.insert( "year", Meta::valYear );
-    m_fieldMetaValues.insert( "comment", Meta::valComment );
-    m_fieldMetaValues.insert( "track number", Meta::valTrackNr );
-    m_fieldMetaValues.insert( "disc number", Meta::valDiscNr );
-    m_fieldMetaValues.insert( "length", Meta::valLength );
-    m_fieldMetaValues.insert( "create date", Meta::valCreateDate);
-    m_fieldMetaValues.insert( "score", Meta::valScore );
-    m_fieldMetaValues.insert( "rating", Meta::valRating );
-    m_fieldMetaValues.insert( "first played", Meta::valFirstPlayed );
-    m_fieldMetaValues.insert( "last played", Meta::valLastPlayed );
-    m_fieldMetaValues.insert( "play count", Meta::valPlaycount );
-    m_fieldMetaValues.insert( "label", Meta::valLabel );
-    m_fieldMetaValues.insert( "url", Meta::valUrl );
-
-    m_fieldPrettyNames.insert( "url", i18n("url") );
-    m_fieldPrettyNames.insert( "title", i18n("title") );
-    m_fieldPrettyNames.insert( "artist name", i18n("artist name") );
-    m_fieldPrettyNames.insert( "album name", i18n("album name") );
-    m_fieldPrettyNames.insert( "genre", i18n("genre") );
-    m_fieldPrettyNames.insert( "composer", i18n("composer") );
-    m_fieldPrettyNames.insert( "year", i18n("year") );
-    m_fieldPrettyNames.insert( "comment", i18n("comment") );
-    m_fieldPrettyNames.insert( "track number", i18n("track number") );
-    m_fieldPrettyNames.insert( "disc number", i18n("disc number") );
-    m_fieldPrettyNames.insert( "length", i18n("length") );
-    m_fieldPrettyNames.insert( "create date", i18n("added to collection") );
-    m_fieldPrettyNames.insert( "score", i18n("score") );
-    m_fieldPrettyNames.insert( "rating", i18n("rating") );
-    m_fieldPrettyNames.insert( "first played", i18n("first played") );
-    m_fieldPrettyNames.insert( "last played", i18n("last played") );
-    m_fieldPrettyNames.insert( "play count", i18n("play count") );
-    m_fieldPrettyNames.insert( "label", i18n("label") );
-}
-
-ConstraintTypes::TagMatchFieldsModel::~TagMatchFieldsModel()
-{
-}
-
-int
-ConstraintTypes::TagMatchFieldsModel::rowCount( const QModelIndex& parent ) const
-{
-    Q_UNUSED( parent )
-    return m_fieldNames.length();
-}
-
-QVariant
-ConstraintTypes::TagMatchFieldsModel::data( const QModelIndex& idx, int role ) const
-{
-    QString s = m_fieldNames.at( idx.row() );
-
-    switch ( role ) {
-        case Qt::DisplayRole:
-        case Qt::EditRole:
-            return QVariant( m_fieldPrettyNames.value( s ) );
-            break;
-        default:
-            return QVariant();
-    }
-    return QVariant();
-}
-
-bool
-ConstraintTypes::TagMatchFieldsModel::contains( const QString& s ) const
-{
-    return m_fieldNames.contains( s );
-}
-
-int
-ConstraintTypes::TagMatchFieldsModel::index_of( const QString& s ) const
-{
-    return m_fieldNames.indexOf( s );
-}
-
-QString
-ConstraintTypes::TagMatchFieldsModel::field_at( int idx ) const
-{
-    if ( ( idx >= 0 ) && ( idx < m_fieldNames.length() ) )
-        return m_fieldNames.at( idx );
-    else
-        return QString();
-}
-
-qint64
-ConstraintTypes::TagMatchFieldsModel::meta_value_of( const QString& f ) const
-{
-    return m_fieldMetaValues.value( f );
-}
-
-QString
-ConstraintTypes::TagMatchFieldsModel::pretty_name_of( const QString& f ) const
-{
-    return m_fieldPrettyNames.value( f );
-}
-
-ConstraintTypes::TagMatch::FieldTypes
-ConstraintTypes::TagMatchFieldsModel::type_of( const QString& f ) const
-{
-    return m_fieldTypes.value( f );
-}
diff --git a/src/playlistgenerator/constraints/TagMatchSupport.cpp b/src/playlistgenerator/constraints/TagMatchSupport.cpp
new file mode 100644
index 0000000..297c589
--- /dev/null
+++ b/src/playlistgenerator/constraints/TagMatchSupport.cpp
@@ -0,0 +1,334 @@
+/****************************************************************************************
+ * Copyright (c) 2010 Soren Harward <stharward@gmail.com>                               *
+ *                                                                                      *
+ * This program is free software; you can redistribute it and/or modify it under        *
+ * the terms of the GNU General Public License as published by the Free Software        *
+ * Foundation; either version 2 of the License, or (at your option) any later           *
+ * version.                                                                             *
+ *                                                                                      *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
+ *                                                                                      *
+ * You should have received a copy of the GNU General Public License along with         *
+ * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#define DEBUG_PREFIX "Constraint::TagMatchSupport"
+
+#include "TagMatch.h"
+
+#include "core/meta/Meta.h"
+#include "core/meta/support/MetaConstants.h"
+#include "core/support/Debug.h"
+
+#include <math.h>
+
+ConstraintTypes::TagMatchFieldsModel::TagMatchFieldsModel()
+{
+    m_fieldNames << "url"
+                 << "title"
+                 << "artist name"
+                 << "album name"
+                 << "genre"
+                 << "composer"
+                 << "year"
+                 << "comment"
+                 << "track number"
+                 << "disc number"
+                 << "length"
+                 << "score"
+                 << "rating"
+                 << "create date"
+                 << "first played"
+                 << "last played"
+                 << "play count"
+                 << "label";
+
+    m_fieldTypes.insert( "url", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "title", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "artist name", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "album name", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "genre", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "composer", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "year", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "comment", TagMatch::FieldTypeString );
+    m_fieldTypes.insert( "track number", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "disc number", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "length", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "create date", TagMatch::FieldTypeDate);
+    m_fieldTypes.insert( "score", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "rating", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "first played", TagMatch::FieldTypeDate );
+    m_fieldTypes.insert( "last played", TagMatch::FieldTypeDate );
+    m_fieldTypes.insert( "play count", TagMatch::FieldTypeInt );
+    m_fieldTypes.insert( "label", TagMatch::FieldTypeString );
+
+    m_fieldMetaValues.insert( "url", Meta::valUrl );
+    m_fieldMetaValues.insert( "title", Meta::valTitle );
+    m_fieldMetaValues.insert( "artist name", Meta::valArtist );
+    m_fieldMetaValues.insert( "album name", Meta::valAlbum );
+    m_fieldMetaValues.insert( "genre", Meta::valGenre );
+    m_fieldMetaValues.insert( "composer", Meta::valComposer );
+    m_fieldMetaValues.insert( "year", Meta::valYear );
+    m_fieldMetaValues.insert( "comment", Meta::valComment );
+    m_fieldMetaValues.insert( "track number", Meta::valTrackNr );
+    m_fieldMetaValues.insert( "disc number", Meta::valDiscNr );
+    m_fieldMetaValues.insert( "length", Meta::valLength );
+    m_fieldMetaValues.insert( "create date", Meta::valCreateDate);
+    m_fieldMetaValues.insert( "score", Meta::valScore );
+    m_fieldMetaValues.insert( "rating", Meta::valRating );
+    m_fieldMetaValues.insert( "first played", Meta::valFirstPlayed );
+    m_fieldMetaValues.insert( "last played", Meta::valLastPlayed );
+    m_fieldMetaValues.insert( "play count", Meta::valPlaycount );
+    m_fieldMetaValues.insert( "label", Meta::valLabel );
+
+    m_fieldPrettyNames.insert( "url", i18n("url") );
+    m_fieldPrettyNames.insert( "title", i18n("title") );
+    m_fieldPrettyNames.insert( "artist name", i18n("artist name") );
+    m_fieldPrettyNames.insert( "album name", i18n("album name") );
+    m_fieldPrettyNames.insert( "genre", i18n("genre") );
+    m_fieldPrettyNames.insert( "composer", i18n("composer") );
+    m_fieldPrettyNames.insert( "year", i18n("year") );
+    m_fieldPrettyNames.insert( "comment", i18n("comment") );
+    m_fieldPrettyNames.insert( "track number", i18n("track number") );
+    m_fieldPrettyNames.insert( "disc number", i18n("disc number") );
+    m_fieldPrettyNames.insert( "length", i18n("length") );
+    m_fieldPrettyNames.insert( "create date", i18n("added to collection") );
+    m_fieldPrettyNames.insert( "score", i18n("score") );
+    m_fieldPrettyNames.insert( "rating", i18n("rating") );
+    m_fieldPrettyNames.insert( "first played", i18n("first played") );
+    m_fieldPrettyNames.insert( "last played", i18n("last played") );
+    m_fieldPrettyNames.insert( "play count", i18n("play count") );
+    m_fieldPrettyNames.insert( "label", i18n("label") );
+}
+
+ConstraintTypes::TagMatchFieldsModel::~TagMatchFieldsModel()
+{
+}
+
+int
+ConstraintTypes::TagMatchFieldsModel::rowCount( const QModelIndex& parent ) const
+{
+    Q_UNUSED( parent )
+    return m_fieldNames.length();
+}
+
+QVariant
+ConstraintTypes::TagMatchFieldsModel::data( const QModelIndex& idx, int role ) const
+{
+    QString s = m_fieldNames.at( idx.row() );
+
+    switch ( role ) {
+        case Qt::DisplayRole:
+        case Qt::EditRole:
+            return QVariant( m_fieldPrettyNames.value( s ) );
+            break;
+        default:
+            return QVariant();
+    }
+    return QVariant();
+}
+
+bool
+ConstraintTypes::TagMatchFieldsModel::contains( const QString& s ) const
+{
+    return m_fieldNames.contains( s );
+}
+
+int
+ConstraintTypes::TagMatchFieldsModel::index_of( const QString& s ) const
+{
+    return m_fieldNames.indexOf( s );
+}
+
+QString
+ConstraintTypes::TagMatchFieldsModel::field_at( int idx ) const
+{
+    if ( ( idx >= 0 ) && ( idx < m_fieldNames.length() ) )
+        return m_fieldNames.at( idx );
+    else
+        return QString();
+}
+
+qint64
+ConstraintTypes::TagMatchFieldsModel::meta_value_of( const QString& f ) const
+{
+    return m_fieldMetaValues.value( f );
+}
+
+QString
+ConstraintTypes::TagMatchFieldsModel::pretty_name_of( const QString& f ) const
+{
+    return m_fieldPrettyNames.value( f );
+}
+
+ConstraintTypes::TagMatch::FieldTypes
+ConstraintTypes::TagMatchFieldsModel::type_of( const QString& f ) const
+{
+    return m_fieldTypes.value( f );
+}
+
+/*************************************
+**************************************/
+
+ConstraintTypes::TagMatch::Comparer::Comparer() : m_dateWeight( 1209600.0 )
+{
+    m_numFieldWeight.insert( Meta::valYear, 8.0 );
+    m_numFieldWeight.insert( Meta::valTrackNr, 5.0 );
+    m_numFieldWeight.insert( Meta::valDiscNr, 0.75 );
+    m_numFieldWeight.insert( Meta::valLength, 100000.0 );
+    m_numFieldWeight.insert( Meta::valScore, 20.0 );
+    m_numFieldWeight.insert( Meta::valRating, 3.0 );
+    m_numFieldWeight.insert( Meta::valPlaycount, 4.0 );
+}
+
+ConstraintTypes::TagMatch::Comparer::~Comparer()
+{
+}
+
+double
+ConstraintTypes::TagMatch::Comparer::compareNum( const double test,
+                                                 const int comparison,
+                                                 const double target,
+                                                 const double strictness,
+                                                 const qint64 field ) const
+{
+    const double weight = m_numFieldWeight.value( field );
+
+    if ( comparison == CompareNumEquals ) {
+        // fuzzy equals -- within 1%, or within 0.001
+        if ( ( abs( test - target ) < ( abs( test + target ) / 200.0 ) ) || ( abs( test - target ) < 0.001 ) ) {
+            return 1.0;
+        } else {
+            return fuzzyProb( test, target, strictness, weight );
+        }
+    } else if ( comparison == CompareNumGreaterThan ) {
+        return ( test > target ) ? 1.0 : fuzzyProb( test, target, strictness, weight );
+    } else if ( comparison == CompareNumLessThan ) {
+        return ( test < target ) ? 1.0 : fuzzyProb( test, target, strictness, weight );
+    } else {
+        return 0.0;
+    }
+    return 0.0;
+}
+
+double
+ConstraintTypes::TagMatch::Comparer::compareStr( const QString& test,
+                                                 const int comparison,
+                                                 const QString& target ) const
+{
+    if ( comparison == CompareStrEquals ) {
+        if ( test.compare( target, Qt::CaseInsensitive ) == 0 )
+            return 1.0;
+    } else if ( comparison == CompareStrStartsWith ) {
+        if ( test.startsWith( target, Qt::CaseInsensitive ) )
+            return 1.0;
+    } else if ( comparison == CompareStrEndsWith ) {
+        if ( test.endsWith( target, Qt::CaseInsensitive ) )
+            return 1.0;
+    } else if ( comparison == CompareStrContains ) {
+        if ( test.contains( target, Qt::CaseInsensitive ) )
+            return 1.0;
+    } else if ( comparison == CompareStrRegExp ) {
+        QRegExp rx( target );
+        if ( rx.indexIn( test ) >= 0 )
+            return 1.0;
+    } else {
+        return 0.0;
+    }
+    return 0.0;
+}
+
+
+double
+ConstraintTypes::TagMatch::Comparer::compareDate( const uint test,
+                                                  const int comparison,
+                                                  const QVariant& targetVar,
+                                                  const double strictness ) const
+{
+    const double weight = m_dateWeight;
+
+    int comp = comparison;
+    uint target = 0;
+    if ( comparison == CompareDateWithin ) {
+        comp = CompareDateAfter;
+        QDateTime now = QDateTime::currentDateTime();
+        DateRange r = targetVar.value<DateRange>();
+        switch ( r.second ) {
+            case 0:
+                target = now.addDays( -1 * r.first ).toTime_t();
+                break;
+            case 1:
+                target = now.addMonths( -1 * r.first ).toTime_t();
+                break;
+            case 2:
+                target = now.addYears( -1 * r.first ).toTime_t();
+                break;
+            default:
+                break;
+        }
+    } else {
+        target = targetVar.value<uint>();
+    }
+
+    if ( comp == CompareDateOn ) {
+        // fuzzy equals -- within 1%, or within 10.0
+        if ( ( abs( test - target ) < ( abs( test + target ) / 200.0 ) ) || ( abs( test - target ) < 10.0 ) ) {
+            return 1.0;
+        } else {
+            return fuzzyProb( static_cast<double>(test), static_cast<double>(target), strictness, weight );
+        }
+    } else if ( comp == CompareDateAfter ) {
+        return ( test > target ) ? 1.0 : fuzzyProb( static_cast<double>(test), static_cast<double>(target), strictness, weight );
+    } else if ( comp == CompareDateBefore ) {
+        return ( test < target ) ? 1.0 : fuzzyProb( static_cast<double>(test), static_cast<double>(target), strictness, weight );
+    } else {
+        return 0.0;
+    }
+    return 0.0;
+}
+
+double
+ConstraintTypes::TagMatch::Comparer::compareLabels( const Meta::TrackPtr t, const int comparison, const QString& target ) const
+{
+    Meta::LabelList labelList = t->labels();
+
+    double v = 0.0;
+    foreach ( Meta::LabelPtr label, labelList ) {
+        // this is technically more correct ...
+        // v = qMax( compare( label->prettyName(), comparison, target ), v );
+
+        // ... but as long as compareStr() returns only 0.0 or 1.0, the following is faster:
+        v = compareStr( label->prettyName(), comparison, target );
+        if ( v > 0.99 ) {
+            return 1.0;
+        }
+    }
+
+    return v;
+}
+
+uint
+ConstraintTypes::TagMatch::Comparer::rangeDate( const double strictness ) const
+{
+    if ( strictness > 0.99 ) return 0;
+    const double s = strictness * strictness;
+    return static_cast<uint>( ceil( 0.460517 * m_dateWeight / ( 0.1 + s ) ) );
+}
+
+int
+ConstraintTypes::TagMatch::Comparer::rangeNum( const double strictness, const qint64 field ) const
+{
+    if ( strictness > 0.99 ) return 0;
+    const double s = strictness * strictness;
+    const double w = m_numFieldWeight.value( field );
+    return static_cast<int>( ceil( 0.460517 * w / ( 0.1 + s ) ) );
+}
+
+double
+ConstraintTypes::TagMatch::Comparer::fuzzyProb( const double a, const double b, const double strictness, const double w ) const
+{
+    const double s = strictness * strictness;
+    return exp( -10.0 * ( 0.1 + s ) / w * ( 1 + abs( a - b ) ) );
+}
\ No newline at end of file