| |   |
| /**************************************************************************************** |
| * Copyright (c) 2008-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::SimilarityChain" |
|
| #include "Debug.h" |
| #include "Fingerprint.h" |
| #include "QueryMaker.h" |
| #include "SimilarityChain.h" |
| #include "meta/capabilities/FingerprintCapability.h" |
| #include "meta/Meta.h" |
| #include "playlistgenerator/Constraint.h" |
| #include "playlistgenerator/ConstraintFactory.h" |
|
| #include <QtGlobal> |
| #include <QtGui> |
| #include <math.h> |
| #include <stdlib.h> |
|
| Constraint* |
| ConstraintTypes::SimilarityChain::createFromXml( QDomElement& xmlelem, ConstraintNode* p ) |
| { |
| if ( p ) { |
| return new |
| ConstraintTypes::SimilarityChain( xmlelem, p ); |
| } else { |
| return 0; |
| } |
| } |
|
| Constraint* |
| ConstraintTypes::SimilarityChain::createNew( ConstraintNode* p ) |
| { |
| if ( p ) { |
| return new |
| ConstraintTypes::SimilarityChain( p ); |
| } else { |
| return 0; |
| } |
| } |
|
| ConstraintFactoryEntry* |
| ConstraintTypes::SimilarityChain::registerMe() |
| { |
| return new ConstraintFactoryEntry( "SimilarityChain", |
| "Smoothes the playlist by making each track similar to the ones around it", |
| &ConstraintTypes::SimilarityChain::createFromXml, &SimilarityChain::createNew ); |
| } |
|
| ConstraintTypes::SimilarityChain::SimilarityChain( QDomElement& xmlelem, ConstraintNode* p ) |
| : Constraint( xmlelem, p ) |
| { |
| QDomAttr a; |
|
| a = xmlelem.attributeNode( "similarity" ); |
| if ( !a.isNull() ) |
| m_similarity = a.value().toDouble(); |
|
| a = xmlelem.attributeNode( "comparison" ); |
| if ( !a.isNull() ) { |
| QString c = a.value(); |
| if ( c == "equals" ) { |
| m_comparison = Constraint::CompareNumEquals; |
| } else if ( c == "greaterthan" ) { |
| m_comparison = Constraint::CompareNumGreaterThan; |
| } else if ( c == "lessthan" ) { |
| m_comparison = Constraint::CompareNumLessThan; |
| } |
| } |
| } |
|
| ConstraintTypes::SimilarityChain::SimilarityChain( ConstraintNode* p ) |
| : Constraint( p ) |
| , m_similarity( 0.75 ) |
| , m_comparison( Constraint::CompareNumGreaterThan ) |
| { |
| setName( "new SimilarityChain constraint" ); |
| } |
|
| QWidget* |
| ConstraintTypes::SimilarityChain::editWidget() const |
| { |
| SimilarityChainEditWidget* e = new SimilarityChainEditWidget( getName(), m_comparison, static_cast<int>( 100*m_similarity ) ); |
| connect( e, SIGNAL( nameChanged( const QString& ) ), this, SLOT( setName( const QString& ) ) ); |
| connect( e, SIGNAL( comparisonChanged( const int ) ), this, SLOT( setComparison( const int ) ) ); |
| connect( e, SIGNAL( similarityChanged( const int ) ), this, SLOT( setSimilarity( const int ) ) ); |
| return e; |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::toXml( QDomDocument& doc, QDomElement& elem ) const |
| { |
| QDomElement c = doc.createElement( "constraint" ); |
| QDomText t = doc.createTextNode( getName() ); |
| c.appendChild( t ); |
| c.setAttribute( "type", "SimilarityChain" ); |
| if ( m_comparison == Constraint::CompareNumEquals ) { |
| c.setAttribute( "comparison", "equals" ); |
| } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { |
| c.setAttribute( "comparison", "greaterthan" ); |
| } else if ( m_comparison == Constraint::CompareNumLessThan ) { |
| c.setAttribute( "comparison", "lessthan" ); |
| } |
| c.setAttribute( "similarity", QString::number( m_similarity ) ); |
| elem.appendChild( c ); |
| } |
|
| QueryMaker* |
| ConstraintTypes::SimilarityChain::initQueryMaker( QueryMaker* qm ) const |
| { |
| return qm; |
| } |
|
| // TODO: check tracks other than just the one immediately before |
| double |
| ConstraintTypes::SimilarityChain::satisfaction( const Meta::TrackList& tl ) |
| { |
| if ( tl.size() <= 1 ) |
| return 1.0; |
|
| double sum = 0.0; |
| for ( int i = 1; i < tl.size(); i++ ) { |
| Meta::TrackPtr t = tl.at( i ); |
| if ( t->hasCapabilityInterface( Meta::Capability::Fingerprint ) ) { |
| Meta::FingerprintCapability* fpc = t->create<Meta::FingerprintCapability>(); |
| sum += simSatisfaction( fpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } else { |
| debug() << t->prettyName() << "does not have a fingerprint"; |
| } |
| } |
| m_satisfaction = sum / ( double )tl.size(); |
| return m_satisfaction; |
| } |
|
| double |
| ConstraintTypes::SimilarityChain::deltaS_insert( const Meta::TrackList& tl, const Meta::TrackPtr t, const int i ) const |
| { |
| if ( tl.size() <= 0 ) |
| return 0.0; |
|
| double oldSum = 0.0; |
| double newSum = 0.0; |
|
| Meta::TrackPtr newT = t; |
| Meta::FingerprintCapability* newfpc = newT->create<Meta::FingerprintCapability>(); |
|
| if ( i >= 1 ) { |
| newSum += simSatisfaction( newfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } |
|
| if ( i < ( tl.size() ) ) { |
| Meta::TrackPtr oldT = tl.at( i ); |
| Meta::FingerprintCapability* oldfpc = oldT->create<Meta::FingerprintCapability>(); |
|
| newSum += simSatisfaction( newfpc->calcSimilarityTo( oldT ) ); |
| if ( i >= 1 ) { |
| oldSum += simSatisfaction( oldfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } |
| } |
|
| double x = m_satisfaction * tl.size(); |
| return ( x + newSum - oldSum ) / ( double )( tl.size() + 1 ) - m_satisfaction; |
| } |
|
| double |
| ConstraintTypes::SimilarityChain::deltaS_replace( const Meta::TrackList& tl, const Meta::TrackPtr t, const int i ) const |
| { |
| if ( tl.size() <= 1 ) |
| return 0.0; |
|
| double oldSum = 0.0; |
| double newSum = 0.0; |
|
| Meta::TrackPtr oldT = tl.at( i ); |
| Meta::FingerprintCapability* oldfpc = oldT->create<Meta::FingerprintCapability>(); |
|
| Meta::TrackPtr newT = t; |
| Meta::FingerprintCapability* newfpc = newT->create<Meta::FingerprintCapability>(); |
|
| if ( i >= 1 ) { |
| oldSum += simSatisfaction( oldfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| newSum += simSatisfaction( newfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } |
|
| if ( i < ( tl.size() - 1 ) ) { |
| oldSum += simSatisfaction( oldfpc->calcSimilarityTo( tl.at( i + 1 ) ) ); |
| newSum += simSatisfaction( newfpc->calcSimilarityTo( tl.at( i + 1 ) ) ); |
| } |
|
| return ( newSum -oldSum ) / ( double )tl.size(); |
| } |
|
| double |
| ConstraintTypes::SimilarityChain::deltaS_delete( const Meta::TrackList& tl, const int i ) const |
| { |
| if ( tl.size() <= 2 ) |
| return 0.0; |
|
| double oldSum = 0.0; |
| double newSum = 0.0; |
|
| Meta::TrackPtr oldT = tl.at( i ); |
| Meta::FingerprintCapability* oldfpc = oldT->create<Meta::FingerprintCapability>(); |
|
| if ( i >= 1 ) { |
| oldSum += simSatisfaction( oldfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } |
|
| if ( i < ( tl.size() - 1 ) ) { |
| oldSum += simSatisfaction( oldfpc->calcSimilarityTo( tl.at( i + 1 ) ) ); |
|
| if ( i >= 1 ) { |
| Meta::TrackPtr newT = tl.at( i + 1 ); |
| Meta::FingerprintCapability* newfpc = newT->create<Meta::FingerprintCapability>(); |
| newSum += simSatisfaction( newfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } |
| } |
|
| double x = m_satisfaction * ( double )tl.size(); |
| return ( x + newSum - oldSum ) / ( double )( tl.size() - 1 ) - m_satisfaction; |
| } |
|
| double |
| ConstraintTypes::SimilarityChain::deltaS_swap( const Meta::TrackList& tl, const int i, const int j ) const |
| { |
| if ( tl.size() <= 1 ) |
| return 0.0; |
|
| double oldSum = 0.0; |
| double newSum = 0.0; |
|
| Meta::TrackPtr rightT = tl.at( i ); |
| Meta::FingerprintCapability* rightfpc = rightT->create<Meta::FingerprintCapability>(); |
|
| Meta::TrackPtr leftT = tl.at( j ); |
| Meta::FingerprintCapability* leftfpc = leftT->create<Meta::FingerprintCapability>(); |
|
| // if we don't make a changed copy, then this algorithm fails if the swap points are adjacent |
| Meta::TrackList newTl( tl ); |
| newTl.swap( i, j ); |
|
| if ( i >= 1 ) { |
| oldSum += simSatisfaction( rightfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| newSum += simSatisfaction( leftfpc->calcSimilarityTo( newTl.at( i - 1 ) ) ); |
| } |
|
| if ( i < ( tl.size() - 1 ) ) { |
| oldSum += simSatisfaction( rightfpc->calcSimilarityTo( tl.at( i + 1 ) ) ); |
| newSum += simSatisfaction( leftfpc->calcSimilarityTo( newTl.at( i + 1 ) ) ); |
| } |
|
| if ( j >= 1 ) { |
| oldSum += simSatisfaction( leftfpc->calcSimilarityTo( tl.at( j - 1 ) ) ); |
| newSum += simSatisfaction( rightfpc->calcSimilarityTo( newTl.at( j - 1 ) ) ); |
| } |
|
| if ( j < ( tl.size() - 1 ) ) { |
| oldSum += simSatisfaction( leftfpc->calcSimilarityTo( tl.at( j + 1 ) ) ); |
| newSum += simSatisfaction( rightfpc->calcSimilarityTo( newTl.at( j + 1 ) ) ); |
| } |
|
| return ( newSum -oldSum ) / ( double )tl.size(); |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::insertTrack( const Meta::TrackList& tl, const Meta::TrackPtr t, const int i ) |
| { |
| m_satisfaction += deltaS_insert( tl, t, i ); |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::replaceTrack( const Meta::TrackList& tl, const Meta::TrackPtr t, const int i ) |
| { |
| m_satisfaction += deltaS_replace( tl, t, i ); |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::deleteTrack( const Meta::TrackList& tl, const int i ) |
| { |
| m_satisfaction += deltaS_delete( tl, i ); |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::swapTracks( const Meta::TrackList& tl, const int i, const int j ) |
| { |
| m_satisfaction += deltaS_swap( tl, i, j ); |
| } |
|
| #ifndef KDE_NO_DEBUG_OUTPUT |
| void |
| ConstraintTypes::SimilarityChain::audit( const Meta::TrackList& tl ) const |
| { |
| if ( tl.size() <= 0 ) |
| return; |
|
| debug() << tl.at( 0 )->prettyName(); |
| for ( int i = 1; i < tl.size(); i++ ) { |
| Meta::TrackPtr t = tl.at( i ); |
| Meta::FingerprintCapability* tfpc = t->create<Meta::FingerprintCapability>(); |
| debug() << "audit" << t->prettyName() << " " << tfpc->calcSimilarityTo( tl.at( i - 1 ) ) << " " << simSatisfaction( tfpc->calcSimilarityTo( tl.at( i - 1 ) ) ); |
| } |
| } |
| #endif |
|
| double |
| ConstraintTypes::SimilarityChain::simSatisfaction( const double s ) const |
| { |
| //return s; |
| if ( s != Fingerprint::INVALID_SIMILARITY ) { |
| switch ( m_comparison ) { |
| case Constraint::CompareNumEquals: |
| return exp( -4.0 * fabs( m_similarity - s ) ); |
| case Constraint::CompareNumGreaterThan: |
| return qMin( 1.0, exp( -4.0 * ( m_similarity - s ) ) ); |
| case Constraint::CompareNumLessThan: |
| return qMin( 1.0, exp( -4.0 * ( s - m_similarity ) ) ); |
| default: |
| return 0.0; |
| } |
| } |
| return 0.0; |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::setComparison( const int c ) |
| { |
| m_comparison = c; |
| } |
|
| void |
| ConstraintTypes::SimilarityChain::setSimilarity( const int sv ) |
| { |
| m_similarity = static_cast<double>( sv ) / 100.0; |
| } |
|
| /****************************** |
| * Edit Widget * |
| ******************************/ |
|
| ConstraintTypes::SimilarityChainEditWidget::SimilarityChainEditWidget( const QString& name, |
| const int comparison, |
| const int similarity ) : QWidget( 0 ) |
| { |
| ui.setupUi( this ); |
|
| ui.lineEdit_Name->setText( name ); |
| ui.comboBox_Comparison->setCurrentIndex( comparison ); |
| ui.slider_Similarity->setValue( similarity ); |
| ui.label_SimilarityValue->setText( QString( "%1%" ).arg( similarity, 3 ) ); |
| } |
|
| void |
| ConstraintTypes::SimilarityChainEditWidget::on_lineEdit_Name_textChanged( const QString& s ) |
| { |
| emit nameChanged( s ); |
| emit updated(); |
| } |
|
| void |
| ConstraintTypes::SimilarityChainEditWidget::on_comboBox_Comparison_currentIndexChanged( const int v ) |
| { |
| emit comparisonChanged( v ); |
| emit updated(); |
| } |
|
| void |
| ConstraintTypes::SimilarityChainEditWidget::on_slider_Similarity_valueChanged( const int v ) |
| { |
| ui.label_SimilarityValue->setText( QString( "%1%" ).arg( v, 3 ) ); |
| emit similarityChanged( v ); |
| emit updated(); |
| } |