]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/score-formula'
authorAndre Noll <maan@tuebingen.mpg.de>
Thu, 18 Nov 2021 15:23:20 +0000 (16:23 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Thu, 18 Nov 2021 15:24:29 +0000 (16:24 +0100)
A single patch which modifies the score function of the audio file
selector to re-scale the two quantities that comprise the score value.

Cooking for six months.

* refs/heads/t/score-formula:
  Rework score formula.

1  2 
NEWS.md
mood.c

diff --combined NEWS.md
index 809da10ac55292f9dfc8fb443eb1c5a43d84fd15,3e3e7abfda28365cba7abfa2ead1c2c408d75801..073f26471a101310cc92e484f4c410aa64a6f9d2
+++ b/NEWS.md
@@@ -1,55 -1,6 +1,56 @@@
  NEWS
  ====
  
 +---------------------------------------
 +0.7.0 (to be announced) "seismic orbit"
 +---------------------------------------
 +
 +- Paraslash writers handle early end-of-file more gracefully.
 +- The alsa writer no longer warns about spurious underruns.
++- The score formula  of the audio file selector has been reworked.
 +- Cleanups of the doubly linked lists code.
 +
 +--------------------------------------
 +0.6.4 (2021-11-04) "fuzzy calibration"
 +--------------------------------------
 +
 +This point release contains a fair number of fixes but no new features.
 +This marks the end of the 0.6 development, although paraslash-0.6 will
 +still be supported for some time and subsequent maintenance releases
 +may follow.
 +
 +- The udp sender no longer crashes when empty chunks are encountered.
 +- Fix a double-free bug in the exit path of the server.
 +- The "jmp" command now errors out when given a negative percentage.
 +- A fix for a bug in para_afh which triggered on the attempt to modify
 +  the tags of an invalid mp4 file.
 +- A memory leak in para_afh has been fixed.
 +- The udp sender no longer sends multiple EOF packets.
 +- Some gcc warnings have been silenced.
 +- Minor log level adjustments and documentation improvements.
 +
 +Downloads:
 +[tarball](./releases/paraslash-0.6.4.tar.xz),
 +[signature](./releases/paraslash-0.6.4.tar.xz.asc)
 +
 +---------------------------------------
 +0.5.9 (2021-11-04) "reversed dimension"
 +---------------------------------------
 +
 +This release contains a few important fixes which have accumulated in
 +the maint branch. The paraslash-0.5.x series has now reached its end
 +of life and will no longer be supported. All users should upgrade to
 +a more recent version at this point.
 +
 +- Fix an issue with the bash completion script.
 +- Initialize the random seed also when using libgrypt.
 +- Fix some compiler warnings in the resample filter
 +- Don't return spurious errors from the ff server command.
 +
 +Downloads:
 +[tarball](./releases/paraslash-0.5.9.tar.bz2),
 +[signature](./releases/paraslash-0.5.9.tar.bz2.asc)
 +
  ----------------------------------------
  0.6.3 (2021-02-18) "generalized activity"
  -----------------------------------------
diff --combined mood.c
index d76cbaf362cccbc305c9f26543440bfb8ffbe590,a5d2b025c479bf7907a80e4df22a83fc06791d8e..bf3f39fa26934fd832b5847f034fdd151602145b
--- 1/mood.c
--- 2/mood.c
+++ b/mood.c
@@@ -39,10 -39,16 +39,16 @@@ struct afs_statistics 
        int64_t num_played_qd;
        /** Quadratic deviation of last played time. */
        int64_t last_played_qd;
+       /** Correction factor for the num played score. */
+       int64_t num_played_correction;
+       /** Correction factor for the last played score. */
+       int64_t last_played_correction;
+       /** Common divisor of the correction factors. */
+       int64_t normalization_divisor;
        /** Number of admissible files */
        unsigned num;
  };
- static struct afs_statistics statistics;
+ static struct afs_statistics statistics = {.normalization_divisor = 1};
  
  /**
   * Each line of the current mood corresponds to a mood_item.
@@@ -263,9 -269,9 +269,9 @@@ static struct mood *alloc_new_mood(cons
        struct mood *m = para_calloc(sizeof(struct mood));
        if (name)
                m->name = para_strdup(name);
 -      INIT_LIST_HEAD(&m->accept_list);
 -      INIT_LIST_HEAD(&m->deny_list);
 -      INIT_LIST_HEAD(&m->score_list);
 +      init_list_head(&m->accept_list);
 +      init_list_head(&m->deny_list);
 +      init_list_head(&m->score_list);
        return m;
  }
  
@@@ -499,20 -505,59 +505,59 @@@ int mood_check_callback(struct afs_call
                check_mood));
  }
  
- static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
- {
-       if (!n || !qd)
-               return 0;
-       return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd);
- }
+ /*
+  * The normalized num_played and last_played values are defined as
+  *
+  *    nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l
+  *
+  *  For a (hypothetical) file with np = 0 and lp = now we thus have
+  *
+  *    nn =  mean_n / sigma_n =: hn > 0
+  *    nl = -(now - mean_l) / sigma_l =: hl < 0
+  *
+  * We design the score function so that both contributions get the same
+  * weight. Define the np and lp score of an arbitrary file as
+  *
+  *    sn := nn * -hl and sl := nl * hn
+  *
+  * Example:
+  *    num_played mean/sigma: 87/14
+  *    last_played mean/sigma: 45/32 days
+  *
+  *    We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying
+  *    nn of every file with the correction factor 1.41 and nl with
+  *    6.21 makes the weight of the two contributions equal.
+  *
+  * The total score s := sn + sl has the representation
+  *
+  *    s = -cn * (np - mean_n) - cl * (lp - mean_l)
+  *
+  * with positive correction factors
+  *
+  *    cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n)
+  *    cl = mean_n / (sqrt(ql) * sqrt(qn) / n)
+  *
+  * where ql and qn are the quadratic deviations stored in the statistics
+  * structure and n is the number of admissible files. To avoid integer
+  * overflows and rounding errors we store the common divisor of the
+  * correction factors separately.
+  */
  static long compute_score(struct afs_info *afsi, long mood_score)
  {
-       mood_score -= normalized_value(afsi->num_played, statistics.num,
-               statistics.num_played_sum, statistics.num_played_qd);
-       mood_score -= normalized_value(afsi->last_played, statistics.num,
-               statistics.last_played_sum, statistics.last_played_qd);
-       return mood_score / 3;
+       int64_t mean_n, mean_l,score_n, score_l;
+       assert(statistics.normalization_divisor > 0);
+       assert(statistics.num > 0);
+       mean_n = statistics.num_played_sum / statistics.num;
+       mean_l = statistics.last_played_sum / statistics.num;
+       score_n = -((int64_t)afsi->num_played - mean_n)
+               * statistics.num_played_correction
+               / statistics.normalization_divisor;
+       score_l = -((int64_t)afsi->last_played - mean_l)
+               * statistics.last_played_correction
+               / statistics.normalization_divisor;
+       return (mood_score + score_n + score_l) / 3;
  }
  
  static int add_afs_statistics(const struct osl_row *row)
@@@ -556,6 -601,7 +601,7 @@@ static int del_afs_statistics(const str
        assert(n);
        if (n == 1) {
                memset(&statistics, 0, sizeof(statistics));
+               statistics.normalization_divisor = 1;
                return 1;
        }
  
@@@ -804,15 -850,11 +850,11 @@@ static int mood_update_audio_file(cons
        return score_update(aft_row, percent);
  }
  
- static void log_statistics(void)
+ /* sse: seconds since epoch. */
+ static void log_statistics(int64_t sse)
  {
        unsigned n = statistics.num;
        int mean_days, sigma_days;
-       /*
-        * We can not use the "now" pointer from sched.c here because we are
-        * called before schedule(), which initializes "now".
-        */
-       struct timeval rnow;
  
        assert(current_mood);
        PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
                return;
        }
        PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
-       clock_get_realtime(&rnow);
-       mean_days = (rnow.tv_sec - statistics.last_played_sum / n) / 3600 / 24;
+       mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24;
        sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
        PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
-       PARA_NOTICE_LOG("num_played mean/sigma: %llu/%llu\n",
-               (long long unsigned)statistics.num_played_sum / n,
-               (long long unsigned)int_sqrt(statistics.num_played_qd / n));
+       PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n",
+               statistics.num_played_sum / n,
+               int_sqrt(statistics.num_played_qd / n));
+       PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n",
+               statistics.num_played_correction);
+       PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n",
+               statistics.last_played_correction);
+       PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n",
+               statistics.normalization_divisor);
  }
  
  /**
@@@ -841,6 -888,25 +888,25 @@@ void close_current_mood(void
        destroy_mood(current_mood);
        current_mood = NULL;
        memset(&statistics, 0, sizeof(statistics));
+       statistics.normalization_divisor = 1;
+ }
+ static void compute_correction_factors(int64_t sse)
+ {
+       struct afs_statistics *s = &statistics;
+       if (s->num > 0) {
+               s->normalization_divisor = int_sqrt(s->last_played_qd)
+                       * int_sqrt(s->num_played_qd) / s->num / 100;
+               s->num_played_correction = sse - s->last_played_sum / s->num;
+               s->last_played_correction = s->num_played_sum / s->num;
+       }
+       if (s->num_played_correction == 0)
+               s->num_played_correction = 1;
+       if (s->normalization_divisor == 0)
+               s->normalization_divisor = 1;
+       if (s->last_played_correction == 0)
+               s->last_played_correction = 1;
  }
  
  /**
@@@ -869,6 -935,11 +935,11 @@@ int change_current_mood(const char *moo
                .size = 0,
                .array = NULL
        };
+       /*
+        * We can not use the "now" pointer from sched.c here because we are
+        * called before schedule(), which initializes "now".
+        */
+       struct timeval rnow;
  
        if (mood_name) {
                struct mood *m;
                        *errmsg = make_message("audio file loop failed");
                return ret;
        }
+       clock_get_realtime(&rnow);
+       compute_correction_factors(rnow.tv_sec);
+       log_statistics(rnow.tv_sec);
        for (i = 0; i < statistics.num; i++) {
                struct admissible_file_info *a = aa.array + i;
                ret = add_to_score_table(a->aft_row, a->score);
                        goto out;
                }
        }
-       log_statistics();
        ret = statistics.num;
  out:
        free(aa.array);