[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-devel
Subject:    Noatun Playlists - Better random song selection
From:       Brendon Higgins <bh_doc () users ! sourceforge ! net>
Date:       2003-04-26 4:47:36
[Download RAW message or body]

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Noatun kicks arse. I usually just turn it on, stick it on random over my whole 
collection, and let it rock away. Though I've noticed that the way songs are 
selected at random during play is a little, well, too random. I, and I assume 
most people, would like it to work similarly to the shuffle op in the 
playlist, but without actually shuffling anything, and still being random 
with songs that have been played a while ago - like repeatedly shuffling the 
tail-end of the list, so to speak.

Currently, the way the next song is selected is completely random with every 
song having the same probability of being selected, thought this is not a 
very "natural" way of doing this. The natural way would be for a song to be 
selected based on things like how long it's been since it was played before, 
relative to the other songs. It should be the case that after a while the 
number of times each song has been played should all be roughly the same.

It came to me the way to do this would be to have weightings associated with 
each song, and have those weightings proportional to how recently the song 
was played previously, or how many times the song has been played relative to 
the average of all songs. So I wrote a program to analyse some statistics of 
such methods (code attached; there is minimal commenting - if something isn't 
obvious just mail me), the most important statistic being the number of steps 
between each play of each individual song. This should be reasonably 
constant, ie. have a low standard deviation for each song, and thus a low 
average of all SDs for each song.

The best way I've found is the method in SimpleWeightedShuffler (the third 
complete Shuffler type) which at first spreads weightings evenly over all the 
songs, and for each song that gets picked its individual weighting is added 
evenly to all the others, and then is set to 0. This way a song will never be 
picked twice in a row, and if a song hasn't been played in a while it becomes 
more and more likely that it will be played. The current method does not 
guarantee this, and I'm always feeling that there are a few songs I haven't 
heard in ages while there are others that Noatun seems to want to play 
ad-nauseum.

I'm not sure if this is an area that anyone is interested in. It may be that 
this method is too bulky for some people, though it'd be fine for my 
purposes. I've had a short look at the split playlist source, but I decided 
I'd better let the pros handle it, first. I'm not likely to make a playlist 
plugin from this, but if anyone else would like to, great!

BTW, I'm not subscribed to the list, so CCs would be appreciated.

Peace,
Brendon Higgins

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)

iD8DBQE+qg9uCTfPD0Uw3q8RAo71AKCmx+Id8/j3sg16hLN4R3jes6Vu5wCgwhk4
NvCJOq84wQwCPzNfXWyGhxA=
=8DWI
-----END PGP SIGNATURE-----

["plshuffler.cpp" (text/x-c++src)]

#include <iostream>
#include <vector>
#include <cstdlib>
#include <cmath>


template<typename T> double mean(std::vector<T>& v) {
    double ret = 0.0;
    for (int i = 0; i < v.size(); ++i) {
        ret += v[i];
    }
    return ret / v.size();
}

template<typename T> double stdDeviation(std::vector<T>& v, double mean) {
    double diff;
    double ret = 0.0;
    for (int i = 0; i < v.size(); ++i) {
        diff = v[i] - mean;
        ret += diff * diff;
    }
    return std::sqrt(ret / (v.size() - 1));
}

class BaseShuffler {
protected:
    std::vector<int> hist;
    std::vector< std::vector<int> > steps;
    std::vector<int> lastSteps;
    // the first steps number for each index should not be counted
    // as it is not representative of continuous operation.
    std::vector<bool> counting;

public:
    BaseShuffler(int m) {
        for (int i = 0; i < m; ++i) {
            hist.push_back(0);
            lastSteps.push_back(0);
            steps.push_back(std::vector<int>());
            counting.push_back(false);
        }
    }

    virtual ~BaseShuffler() {
    }

    virtual void debug() {
    }

    virtual int pick() = 0;

    // to be called after pick
    virtual void picked(int n) {
        ++hist[n];
        if (counting[n]) {
            steps[n].push_back(lastSteps[n]);
        } else {
            counting[n] = true;
        }
        for (int i = 0; i < lastSteps.size(); ++i) {
            ++lastSteps[i];
        }
        lastSteps[n] = 0;
    }

    std::vector<int>& getHist() {
        return hist;
    }

    void printStepStats() {
        std::vector<double> sdStats;
        for (int i = 0; i < steps.size(); ++i) {
            std::cout << "#Picks: " << steps[i].size();
            std::cout << "  Steps: ";
            for (int j = 0; j < steps[i].size(); ++j) {
                std::cout << steps[i][j] << ",";
            }
            double m = mean(steps[i]);
            double sd = stdDeviation(steps[i], m);
            sdStats.push_back(sd);
            std::cout << "  Avg: " << m << "  SD: " << sd << "\n";
        }
        double m = mean(sdStats);
        std::cout << "AvgSD: " << m << "  SDSD: "
            << stdDeviation(sdStats, m) << std::endl;
    }
};

class RandomShuffler: public BaseShuffler {
public:
    RandomShuffler(int m): BaseShuffler(m) {
    }

    virtual ~RandomShuffler() {
    }

    virtual int pick() {
        int p = std::rand() % hist.size();
        picked(p);
        return p;
    }
};

class WeightedShuffler: public BaseShuffler {
protected:
    std::vector<double> weights;

public:
    WeightedShuffler(int m): BaseShuffler(m) {
        // setup weights - all equal
        double p = 1.0 / m;
        for (int i = 0; i < m; ++i) {
            weights.push_back(p);
        }
    }

    virtual ~WeightedShuffler() {
    }

    virtual void debug() {
        double total = 0.0;
        for (int i = 0; i < weights.size(); ++i) {
            total += weights[i];
            std::cout << "  " << weights[i];
        }
        std::cout << "  T: " << total << "\n";
    }
};

class ComplexWeightedShuffler: public WeightedShuffler {
private:
    void histInspect(int* maxPos, int* minPos) {
        *maxPos = 0;
        *minPos = 0;
        for (int i = 1; i < hist.size(); ++i) {
            if (hist[i] < hist[*minPos]) {
                *minPos = i;
            }
            if (hist[i] > hist[*maxPos]) {
                *maxPos = i;
            }
        }
    }

    void calcWeights() {
        // Weights are spread linearly based on how far from the max
        // a given pos is.
        int maxPos, minPos;
        histInspect(&maxPos, &minPos);
        int maxDist = hist[maxPos] - hist[minPos];
        if (maxDist > 0) {
            double totalWeight = 0.0;
            for (int i = 0; i < hist.size(); ++i) {
                weights[i] = hist[maxPos] - hist[i]
                    // Plus some arbitrary enhancement to make it
                    // slightly more random than just "play only
                    // what's not already been played"
                    + double(maxDist) / hist.size();
                totalWeight += weights[i];
            }
            for (int i = 0; i < hist.size(); ++i) {
                weights[i] /= totalWeight;
            }
        } else {
            // There is no difference! Yippie!
            for (int i = 0; i < hist.size(); ++i) {
                weights[i] = 1.0 / hist.size();
            }
        }
    }

public:
    ComplexWeightedShuffler(int m): WeightedShuffler(m) {
    }

    virtual ~ComplexWeightedShuffler() {
    }

    virtual int pick() {
        calcWeights();
        double r = double(std::rand()) / RAND_MAX;
        int pos = -1;
        while (r > 0.0) {
            r -= weights[++pos];
        }
        picked(pos);
        return pos;
    }
};

class SimpleWeightedShuffler: public WeightedShuffler {
public:
    SimpleWeightedShuffler(int m): WeightedShuffler(m) {
    }

    virtual ~SimpleWeightedShuffler() {
    }

    virtual void picked(int n) {
        WeightedShuffler::picked(n);
        double x = weights[n] / (weights.size() - 1);
        for (int i = 0; i < weights.size(); ++i) {
            weights[i] += x;
        }
        weights[n] = 0.0;
    }

    virtual int pick() {
        double r = double(std::rand()) / RAND_MAX;
        int pos = -1;
        while (r > 0.0) {
            r -= weights[++pos];
        }
        picked(pos);
        return pos;
    }
};

void printHist(std::vector<int>& hist, int current) {
    for (int i = 0; i < hist.size(); ++i) {
        std::cout << " ";
        if (i == current) {
            std::cout << "+";
        } else {
            std::cout << " ";
        }
        std::cout << hist[i];
    }
}

void printStats(std::vector<int>& hist) {
    double m = mean(hist);
    std::cout << "  Mean: " << m << "  StdDev: " << stdDeviation(hist, m) << "\n";
}

int main() {
    std::srand(std::time(0));

    int max;
    std::cout << "Max: ";
    std::cin >> max;

    int num;
    std::cout << "Num shuffles: ";
    std::cin >> num;

    int type;
    std::cout << "(1) Random, (2) ComplexWeighted, (3) SimpleWeighted: ";
    std::cin >> type;

    int debug;
    std::cout << "Debug (1/0): ";
    std::cin >> debug;

    BaseShuffler* shuffler;

    if (type == 1) {
        shuffler = new RandomShuffler(max);
    } else if (type == 2) {
        shuffler = new ComplexWeightedShuffler(max);
    } else {
        shuffler = new SimpleWeightedShuffler(max);
    }

    int cur;

    for (int i = 0; i < num; ++i) {
        cur = shuffler->pick();
        //printHist(shuffler->getHist(), cur);
        //printStats(shuffler->getHist());
        if (debug == 1) {
            shuffler->debug();
        }
    }

    shuffler->printStepStats();

    delete shuffler;

    return 0;
}


>> Visit http://mail.kde.org/mailman/listinfo/kde-devel#unsub to unsubscribe <<


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic