]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/rm_v1_moods'
authorAndre Noll <maan@tuebingen.mpg.de>
Thu, 10 Feb 2022 15:34:19 +0000 (16:34 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Thu, 10 Feb 2022 15:35:34 +0000 (16:35 +0100)
A single commit which removes a long obsolete feature.

Cooking for almost a year.

* refs/heads/t/rm_v1_moods:
  Remove support for version 1 moods.

1  2 
NEWS.md
configure.ac
mood.c

diff --combined NEWS.md
index 320dfbd5260c9dfefd4ab0447721c756b28fa96f,3e3e7abfda28365cba7abfa2ead1c2c408d75801..a31aeaf18e04b32b496f47cea04988fd74f5536f
+++ b/NEWS.md
@@@ -1,64 -1,6 +1,65 @@@
  NEWS
  ====
  
 +---------------------------------------
 +0.7.0 (to be announced) "seismic orbit"
 +---------------------------------------
 +
 +- The new "duration" keyword of the mood grammar makes it possible to
 +  impose a constraint on the duration of the admissible files.
++- The long deprecated version 1 mood syntax is no longer supported.
 +- 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.
 +- New option for configure: --enable-ubsan to detect and report undefined
 +  behaviour.
 +- The "tasks" server command has been removed.
 +
 +Downloads:
 +[tarball](./releases/paraslash-git.tar.xz)
 +
 +--------------------------------------
 +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 configure.ac
index 850e814f21cbe3601441f3f625a712290b7d3015,240098589d4374233cfed7b04146ec299d008a61..99e82b2892ef4c3641f91f018c9150582083974d
@@@ -373,11 -373,6 +373,11 @@@ AC_CHECK_HEADER(samplerate.h, [], HAVE_
  AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no)
  LIB_SUBST_FLAGS(samplerate)
  UNSTASH_FLAGS
 +######################################################################### ubsan
 +AC_ARG_ENABLE([ubsan], [AS_HELP_STRING(--enable-ubsan,
 +      [Detect and report undefined behaviour.])],
 +      [ENABLE_UBSAN=yes], [ENABLE_UBSAN=no])
 +AC_SUBST(ENABLE_UBSAN)
  ######################################################################### server
  if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
                test -n "$FLEX"; then
                daemon
                http_send
                close_on_fork
-               mm
                crypt_common
                base64
                ipc
diff --combined mood.c
index bf3f39fa26934fd832b5847f034fdd151602145b,ccc57a0305d5e23152d789bb974b53729171ec25..bbe84734deffd189d7fb8d05ac3e58877b356f8f
--- 1/mood.c
--- 2/mood.c
+++ b/mood.c
@@@ -12,7 -12,6 +12,6 @@@
  #include "afh.h"
  #include "afs.h"
  #include "list.h"
- #include "mm.h"
  #include "mood.h"
  
  /*
@@@ -39,53 -38,15 +38,21 @@@ 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.
-  */
- struct mood_item {
-       /** The method this line is referring to. */
-       const struct mood_method *method;
-       /** The data structure computed by the mood parser. */
-       void *parser_data;
-       /** The given score value, or zero if none was given. */
-       int32_t score_arg;
-       /** Non-zero if random scoring was requested. */
-       int random_score;
-       /** Whether the "not" keyword was given in the mood line. */
-       int logical_not;
-       /** The position in the list of items. */
-       struct list_head mood_item_node;
- };
- /*
-  * Created from the mood definition by \ref change_current_mood().
-  *
-  * When a mood is opened, each line of its definition is investigated, and a
-  * corresponding mood item is produced. Each mood line starts with accept,
-  * deny, or score which determines the type of the mood line. For each such
-  * type a linked list is maintained whose entries are the mood items.
-  */
  struct mood {
        /** The name of this mood. */
        char *name;
-       /** The list of mood items of type \p accept. */
-       struct list_head accept_list;
-       /** The list of mood items of type \p deny. */
-       struct list_head deny_list;
-       /** The list of mood items of type \p score. */
-       struct list_head score_list;
-       /* Only used for version 2 moods. */
+       /** Info for the bison parser. */
        struct mp_context *parser_context;
  };
  
@@@ -152,115 -113,20 +119,20 @@@ __a_const static uint64_t int_sqrt(uint
        return res;
  }
  
- /*
-  * Returns true if row matches, false if it does not match. In any case score
-  * and score_arg_sum are set/increased accordingly.
-  */
- static bool get_item_score(struct mood_item *item, const struct afs_info *afsi,
-               const struct afh_info *afhi, const char *path, long *score,
-               long *score_arg_sum)
- {
-       int ret;
-       bool match = true;
-       *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
-       ret = 100;
-       if (item->method) {
-               ret = item->method->score_function(path, afsi, afhi,
-                       item->parser_data);
-               if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
-                       match = false;
-       }
-       if (item->random_score)
-               *score = PARA_ABS(ret) * para_random(100);
-       else
-               *score = PARA_ABS(ret) * item->score_arg;
-       return match;
- }
  /* returns 1 if row admissible, 0 if not, negative on errors */
- static int row_is_admissible(const struct osl_row *aft_row, struct mood *m,
-               long *scorep)
+ static int row_is_admissible(const struct osl_row *aft_row, struct mood *m)
  {
-       struct mood_item *item;
-       int ret;
-       bool match;
-       long score_arg_sum = 0, score = 0, item_score;
-       struct afs_info afsi;
-       struct afh_info afhi;
-       char *path;
        if (!m)
                return -E_NO_MOOD;
-       if (m->parser_context) {
-               *scorep = 0;
-               return mp_eval_row(aft_row, m->parser_context);
-       }
-       ret = get_afsi_of_row(aft_row, &afsi);
-       if (ret < 0)
-               return ret;
-       ret = get_afhi_of_row(aft_row, &afhi);
-       if (ret < 0)
-               return ret;
-       ret = get_audio_file_path_of_row(aft_row, &path);
-       if (ret < 0)
-               return ret;
-       /* reject audio file if it matches any entry in the deny list */
-       list_for_each_entry(item, &m->deny_list, mood_item_node) {
-               match = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (match) /* not admissible */
-                       return 0;
-               score += item_score;
-       }
-       match = false;
-       list_for_each_entry(item, &m->accept_list, mood_item_node) {
-               ret = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (ret == 0)
-                       continue;
-               match = true;
-               score += item_score;
-       }
-       /* reject if there is no matching entry in the accept list */
-       if (!match && !list_empty(&m->accept_list))
-               return 0;
-       list_for_each_entry(item, &m->score_list, mood_item_node) {
-               match = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (match)
-                       score += item_score;
-       }
-       if (score_arg_sum)
-               score /= score_arg_sum;
-       *scorep = score;
-       return 1;
- }
- static void cleanup_list_entry(struct mood_item *item)
- {
-       if (item->method && item->method->cleanup)
-               item->method->cleanup(item->parser_data);
-       else
-               free(item->parser_data);
-       list_del(&item->mood_item_node);
-       free(item);
+       return mp_eval_row(aft_row, m->parser_context);
  }
  
  static void destroy_mood(struct mood *m)
  {
-       struct mood_item *tmp, *item;
        if (!m)
                return;
-       list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
-               cleanup_list_entry(item);
-       list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
-               cleanup_list_entry(item);
-       list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
-               cleanup_list_entry(item);
-       free(m->name);
        mp_shutdown(m->parser_context);
+       free(m->name);
        free(m);
  }
  
@@@ -269,160 -135,16 +141,16 @@@ 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);
        return m;
  }
  
- /** The different types of a mood line. */
- enum mood_line_type {
-       /** Invalid. */
-       ML_INVALID,
-       /** Accept line. */
-       ML_ACCEPT,
-       /** Deny line. */
-       ML_DENY,
-       /** Score line. */
-       ML_SCORE
- };
- /** Data passed to the parser of a mood line. */
- struct mood_line_parser_data {
-       /** The mood this mood line belongs to. */
-       struct mood *m;
-       /** The line number in the mood definition. */
-       unsigned line_num;
- };
- /*
-  * <accept [with score <score>] | deny [with score <score>]  | score <score>>
-  *    [if] [not] <mood_method> [options]
-  * <score> is either an integer or "random" which assigns a random score to
-  * all matching files
-  */
- static int parse_mood_line(char *mood_line, void *data)
- {
-       struct mood_line_parser_data *mlpd = data;
-       char **argv;
-       unsigned num_words;
-       char **w;
-       int i, ret;
-       enum mood_line_type mlt = ML_INVALID;
-       struct mood_item *mi = NULL;
-       mlpd->line_num++;
-       ret = create_argv(mood_line, " \t", &argv);
-       if (ret < 0)
-               return ret;
-       num_words = ret;
-       if (!num_words) /* empty line */
-               goto out;
-       w = argv;
-       if (**w == '#') /* comment */
-               goto out;
-       if (!strcmp(*w, "accept"))
-               mlt = ML_ACCEPT;
-       else if (!strcmp(*w, "deny"))
-               mlt = ML_DENY;
-       else if (!strcmp(*w, "score"))
-               mlt = ML_SCORE;
-       ret = -E_MOOD_SYNTAX;
-       if (mlt == ML_INVALID)
-               goto out;
-       mi = para_calloc(sizeof(struct mood_item));
-       if (mlt != ML_SCORE) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "with"))
-                       goto check_for_if;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "score"))
-                       goto out;
-       }
-       if (mlt == ML_SCORE || !strcmp(*w, "score")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "random")) {
-                       mi->random_score = 0;
-                       ret = para_atoi32(*w, &mi->score_arg);
-                       if (ret < 0)
-                               goto out;
-               } else {
-                       mi->random_score = 1;
-                       if (!*(w + 1))
-                       goto success; /* the line "score random" is valid */
-               }
-       } else
-               mi->score_arg = 0;
-       ret = -E_MOOD_SYNTAX;
-       w++;
-       if (!*w)
-               goto out;
- check_for_if:
-       if (!strcmp(*w, "if")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-       }
-       if (!strcmp(*w, "not")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               mi->logical_not = 1;
-       } else
-               mi->logical_not = 0;
-       for (i = 0; mood_methods[i].parser; i++) {
-               if (strcmp(*w, mood_methods[i].name))
-                       continue;
-               break;
-       }
-       ret = -E_MOOD_SYNTAX;
-       if (!mood_methods[i].parser)
-               goto out;
-       ret = mood_methods[i].parser(num_words - 1 - (w - argv), w,
-               &mi->parser_data);
-       if (ret < 0)
-               goto out;
-       mi->method = &mood_methods[i];
- success:
-       if (mlpd->m) {
-               if (mlt == ML_ACCEPT)
-                       para_list_add(&mi->mood_item_node, &mlpd->m->accept_list);
-               else if (mlt == ML_DENY)
-                       para_list_add(&mi->mood_item_node, &mlpd->m->deny_list);
-               else
-                       para_list_add(&mi->mood_item_node, &mlpd->m->score_list);
-       }
-       PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
-               (mlt == ML_DENY? "deny" : "score"), mi->method);
-       ret = 1;
- out:
-       free_argv(argv);
-       if (mi && (ret < 0 || !mlpd->m)) { /* mi was not added to any list */
-               free(mi->parser_data);
-               free(mi);
-       }
-       return ret;
- }
  static int load_mood(const struct osl_row *mood_row, struct mood **m,
                char **errmsg)
  {
        char *mood_name;
        struct osl_object mood_def;
-       struct mood_line_parser_data mlpd = {.line_num = 0};
        int ret;
  
-       *m = NULL;
        ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
        if (ret < 0) {
                if (errmsg)
                return ret;
        }
        assert(*mood_name);
-       mlpd.m = alloc_new_mood(mood_name);
-       ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
-               parse_mood_line, &mlpd);
-       if (ret < 0) {
-               PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name);
-               ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context,
-                       errmsg);
-               if (ret < 0)
-                       destroy_mood(mlpd.m);
-       } else {
-               PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name);
-               PARA_WARNING_LOG("please convert to version 2\n");
-               ret = 1;
-       }
+       *m = alloc_new_mood(mood_name);
+       PARA_INFO_LOG("opening mood %s\n", mood_name);
+       ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, errmsg);
        osl_close_disk_object(&mood_def);
-       if (ret >= 0)
-               *m = mlpd.m;
        return ret;
  }
  
  static int check_mood(struct osl_row *mood_row, void *data)
  {
        struct para_buffer *pb = data;
-       char *mood_name;
+       char *mood_name, *errmsg;
        struct osl_object mood_def;
-       struct mood_line_parser_data mlpd = {.line_num = 0};
+       struct mood *m;
        int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
  
        if (ret < 0) {
        }
        if (!*mood_name) /* ignore dummy row */
                goto out;
-       ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
-               parse_mood_line, &mlpd);
+       m = alloc_new_mood("check");
+       ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
+               &errmsg);
        if (ret < 0) {
-               char *errmsg;
-               struct mood *m = alloc_new_mood("check");
-               ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
-                       &errmsg);
-               if (ret < 0) {
-                       para_printf(pb, "%s: %s\n", mood_name, errmsg);
-                       free(errmsg);
-                       para_printf(pb, "%s\n", para_strerror(-ret));
-               } else
-                       destroy_mood(m);
-       } else {
-               para_printf(pb, "%s: v1 mood, please convert to v2\n",
-                       mood_name);
-       }
+               para_printf(pb, "%s: %s\n", mood_name, errmsg);
+               free(errmsg);
+               para_printf(pb, "%s\n", para_strerror(-ret));
+       } else
+               destroy_mood(m);
        ret = 1; /* don't fail the loop on invalid mood definitions */
  out:
        osl_close_disk_object(&mood_def);
@@@ -505,59 -204,20 +210,57 @@@ int mood_check_callback(struct afs_call
                check_mood));
  }
  
- static long compute_score(struct afs_info *afsi, long mood_score)
 +/*
 + * 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 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);
+ }
+ static long compute_score(struct afs_info *afsi)
  {
-       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;
+       long score = -normalized_value(afsi->num_played, statistics.num,
+               statistics.num_played_sum, statistics.num_played_qd);
+       score -= normalized_value(afsi->last_played, statistics.num,
+               statistics.last_played_sum, statistics.last_played_qd);
+       return score / 2;
  }
  
  static int add_afs_statistics(const struct osl_row *row)
@@@ -601,7 -261,6 +304,7 @@@ static int del_afs_statistics(const str
        assert(n);
        if (n == 1) {
                memset(&statistics, 0, sizeof(statistics));
 +              statistics.normalization_divisor = 1;
                return 1;
        }
  
  
  /*
   * At mood open time we determine the set of admissible files for the given
-  * mood. The mood score of each admissible file is computed by adding up all
-  * mood item scores. Next, we update the afs statistics and append a struct
-  * admissible_file_info to a temporary array.
-  *
-  * When all files have been processed in this way, the final score of each
-  * admissible file is computed by adding the dynamic score (which depends on
-  * the afs_statistics and the current time) to the mood score. Finally, all
-  * audio files in the temporary array are added to the score table and the
-  * array is freed.
+  * mood where each file is identified by a pointer to a row of the audio file
+  * table. In the first pass the pointers are added to a temporary array and
+  * statistics are computed. When all admissible files have been processed in
+  * this way, the score of each admissible file is computed and the (row, score)
+  * pair is added to the score table. This has to be done in a second pass
+  * since the score depends on the statistics. Finally, the array is freed.
   */
- struct admissible_file_info
- {
-       /** The admissible audio file. */
-       struct osl_row *aft_row;
-       /** Its score. */
-       long score;
- };
- /** The temporary array of admissible files. */
  struct admissible_array {
        /** Files are admissible wrt. this mood. */
        struct mood *m;
        /** The size of the array */
        unsigned size;
        /** Pointer to the array of admissible files. */
-       struct admissible_file_info *array;
+       struct osl_row **array;
  };
  
- /**
-  * Add an entry to the array of admissible files.
-  *
-  * \param aft_row The audio file to be added.
-  * \param private_data Pointer to a struct admissible_file_info.
-  *
-  * \return 1 if row admissible, 0 if not, negative on errors.
+ /*
+  * Check whether the given audio file is admissible. If it is, add it to array
+  * of admissible files.
   */
  static int add_if_admissible(struct osl_row *aft_row, void *data)
  {
        struct admissible_array *aa = data;
        int ret;
-       long score = 0;
  
-       ret = row_is_admissible(aft_row, aa->m, &score);
+       ret = row_is_admissible(aft_row, aa->m);
        if (ret <= 0)
                return ret;
        if (statistics.num >= aa->size) {
                aa->size *= 2;
                aa->size += 100;
                aa->array = para_realloc(aa->array,
-                       aa->size * sizeof(struct admissible_file_info));
+                       aa->size * sizeof(struct osl_row *));
        }
-       aa->array[statistics.num].aft_row = aft_row;
-       aa->array[statistics.num].score = score;
-       ret = add_afs_statistics(aft_row);
-       if (ret < 0)
-               return ret;
-       return 1;
+       aa->array[statistics.num] = aft_row;
+       return add_afs_statistics(aft_row);
  }
  
  /**
@@@ -750,7 -388,7 +432,7 @@@ static int update_afs_statistics(struc
        return 1;
  }
  
- static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
+ static int add_to_score_table(const struct osl_row *aft_row)
  {
        long score;
        struct afs_info afsi;
  
        if (ret < 0)
                return ret;
-       score = compute_score(&afsi, mood_score);
+       score = compute_score(&afsi);
        return score_add(aft_row, score);
  }
  
@@@ -816,7 -454,7 +498,7 @@@ static int mood_update_audio_file(cons
        if (ret < 0)
                return ret;
        was_admissible = ret;
-       ret = row_is_admissible(aft_row, current_mood, &score);
+       ret = row_is_admissible(aft_row, current_mood);
        if (ret < 0)
                return ret;
        is_admissible = (ret > 0);
                ret = add_afs_statistics(aft_row);
                if (ret < 0)
                        return ret;
-               return add_to_score_table(aft_row, score);
+               return add_to_score_table(aft_row);
        }
        /* update score */
        ret = get_afsi_of_row(aft_row, &afsi);
                if (ret < 0)
                        return ret;
        }
-       score = compute_score(&afsi, score);
+       score = compute_score(&afsi);
        PARA_DEBUG_LOG("score: %li\n", score);
        percent = (score + 100) / 3;
        if (percent > 100)
        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);
  }
  
  /**
@@@ -888,25 -525,6 +570,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;
  }
  
  /**
@@@ -935,11 -553,6 +617,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);
+               ret = add_to_score_table(aa.array[i]);
                if (ret < 0) {
                        if (errmsg)
                                *errmsg = make_message(
                        goto out;
                }
        }
 -      log_statistics();
        ret = statistics.num;
  out:
        free(aa.array);