]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge topic branch t/afs-cleanups into master
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 20 Nov 2022 18:21:35 +0000 (19:21 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 20 Nov 2022 18:23:40 +0000 (19:23 +0100)
A fair number of patches which clean up all parts of the audio file
selector. The most visible change is probably that error messages from
afs callbacks are sent with a proper sideband designator so that they
are written to stderr on the client side.

* refs/heads/t/afs-cleanups: (30 commits)
  Introduce afs_error().
  afs.c: Move com_select() and its callback down.
  Rename mood_switch(), mood_close(), playlist_{open/close}.
  Assume that score_open() and score_clear() always succeed.
  playlist.c: Rename playlist_info -> playlist_instance.
  mood.c: Rename struct mood to mood_instance.
  afs.c: Rename ->handler of struct callback_query to ->cb.
  Simplify and improve activate_mood_or_playlist().
  afs: Replace ->init of afs tables by table operations.
  Merge load_playlist() into playlist_open() and simplify.
  Simplify row_belongs_to_score_table().
  Remove mood.h.
  Clean up and rename change_current_mood().
  mood.c: Simplify and rename load_mood().
  mood.c: Move struct statistics into struct mood.
  afs.c: Improve activate_mood_or_playlist().
  Improve playlist_open().
  blob.c: Don't initialize table pointer in table->init().
  blob: Constify name argument of blob_get_def_by_name().
  Rename admissible_file_loop() -> score_loop().
  ...

1  2 
NEWS.md
error.h
mood.c

diff --combined NEWS.md
index 539f75768c6b99ef6bbbff4074e8fd309692b5c4,5a00175c3255702b03ec86e0af1938863f53ba4d..009982a3b85a3ad2421aab8ef4684b3dc30a8587
+++ b/NEWS.md
@@@ -1,18 -1,6 +1,21 @@@
  NEWS
  ====
  
 +------------------------------------------
 +0.7.2 (to be announced) "optical friction"
 +------------------------------------------
 +
++- A major cleanup of the audio file selector.
++- The client no longer prints error messages from afs commands to
++  stdout but to stderr.
 +- The sleep subcommand of para_mixer gained two options to control
 +  the startup mood and the time period before fade-out starts. A bunch
 +  of further improvements for this subcommand went in as well.
 +- Minor cleanup of the net subsystem.
 +
 +Downloads:
 +[tarball](./releases/paraslash-git.tar.xz)
 +
  --------------------------------------
  0.7.1 (2022-10-03) "digital spindrift"
  --------------------------------------
@@@ -39,7 -27,6 +42,7 @@@ usual mix of bug fixes and minor improv
    requires support from the compiler, the oldest supported gcc version
    has been bumped to gcc-5.4 (released in 2015).
  
 +Downloads:
  [tarball](./releases/paraslash-0.7.1.tar.xz),
  [signature](./releases/paraslash-0.7.1.tar.xz.asc)
  
@@@ -232,23 -219,6 +235,23 @@@ Downloads
  [tarball](./releases/paraslash-0.6.1.tar.xz),
  [signature](./releases/paraslash-0.6.1.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.5.8 (2017-09-23) "branching parabola"
  ---------------------------------------
diff --combined error.h
index f19aa634bf1f8a2306249a8898eacfefd0078a06,4cd8dafcc5e77b3dc005b4bcd5d36c5ab61735af..7c146da2076ae74d896bb82a0e931d8b5b6f49f5
+++ b/error.h
@@@ -2,7 -2,6 +2,7 @@@
  
  /** \file error.h List of error codes and messages. */
  
 +/** \cond para_error */
  /** Codes and messages. */
  #define PARA_ERRORS \
        PARA_ERROR(SUCCESS, "success"), \
        PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
        PARA_ERROR(NOFD, "did not receive open fd from afs"), \
        PARA_ERROR(NO_MATCH, "no matches"), \
-       PARA_ERROR(NO_MOOD, "no mood available"), \
        PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
        PARA_ERROR(NOT_PLAYING, "not playing"), \
        PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \
        PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \
        PARA_ERROR(PATH_FOUND, ""), /* not really an error */ \
        PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
-       PARA_ERROR(PLAYLIST_LOADED, ""), /* not really an error */ \
        PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
        PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
        PARA_ERROR(QUEUE, "packet queue overrun"), \
@@@ -262,7 -259,6 +260,7 @@@ enum para_error_codes {PARA_ERRORS}
  extern const char * const para_errlist[];
  /** Exactly one .c file per executable must define the array. */
  #define DEFINE_PARA_ERRLIST const char * const para_errlist[] = {PARA_ERRORS}
 +/** \endcond para_error */
  
  /**
   * This bit indicates whether a number is considered a system error number
diff --combined mood.c
index d47c54efa360b856c63b660fd4cdb5b81d442b68,15da76f2b20e630e87fca05e674166b59027ac41..9cdfd011a6ef4f2ab9fb180892f6412247eb68fe
--- 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 "mood.h"
  
  /*
   * Mood parser API. It's overkill to have an own header file for
@@@ -47,20 -46,29 +46,29 @@@ struct afs_statistics 
        /** Number of admissible files */
        unsigned num;
  };
- static struct afs_statistics statistics = {.normalization_divisor = 1};
  
- struct mood {
-       /** The name of this mood. */
+ /**
+  * Stores an instance of a loaded mood (parser and statistics).
+  *
+  * A structure of this type is allocated and initialized when a mood is loaded.
+  */
+ struct mood_instance {
+       /** NULL means that this is the "dummy" mood. */
        char *name;
-       /** Info for the bison parser. */
+       /** Bison's abstract syntax tree, used to determine admissibility. */
        struct mp_context *parser_context;
+       /** To compute the score. */
+       struct afs_statistics stats;
  };
  
  /*
-  * If current_mood is NULL then no mood is currently open. If
-  * current_mood->name is NULL, the dummy mood is currently open.
+  * If current_mood is NULL then no mood is currently loaded. If
+  * current_mood->name is NULL, the current mood is the dummy mood.
+  *
+  * The statistics are adjusted dynamically through this pointer as files are
+  * added, removed or played.
   */
- static struct mood *current_mood;
+ static struct mood_instance *current_mood;
  
  /*
   * Find the position of the most-significant set bit.
@@@ -119,15 -127,7 +127,7 @@@ __a_const static uint64_t int_sqrt(uint
        return res;
  }
  
- /* 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)
- {
-       if (!m)
-               return -E_NO_MOOD;
-       return mp_eval_row(aft_row, m->parser_context);
- }
- static void destroy_mood(struct mood *m)
+ static void destroy_mood(struct mood_instance *m)
  {
        if (!m)
                return;
        free(m);
  }
  
- static struct mood *alloc_new_mood(const char *name)
+ static struct mood_instance *alloc_new_mood(const char *name)
  {
-       struct mood *m = zalloc(sizeof(struct mood));
+       struct mood_instance *m = zalloc(sizeof(*m));
        if (name)
                m->name = para_strdup(name);
+       m->stats.normalization_divisor = 1;
        return m;
  }
  
- static int load_mood(const struct osl_row *mood_row, struct mood **m,
-               char **errmsg)
+ static int init_mood_parser(const char *mood_name, struct mood_instance **m,
+               char **err)
  {
-       char *mood_name;
        struct osl_object mood_def;
        int ret;
  
-       ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
+       if (!*mood_name) {
+               if (err)
+                       *err = make_message("empty mood name\n");
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       }
+       ret = mood_get_def_by_name(mood_name, &mood_def);
        if (ret < 0) {
-               if (errmsg)
-                       *errmsg = make_message(
-                               "could not read mood definition");
+               if (err)
+                       *err = make_message("could not read mood definition\n");
                return ret;
        }
-       assert(*mood_name);
        *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);
+       PARA_INFO_LOG("loading mood %s\n", mood_name);
+       ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, err);
        osl_close_disk_object(&mood_def);
        if (ret < 0)
                destroy_mood(*m);
  
  static int check_mood(struct osl_row *mood_row, void *data)
  {
-       struct para_buffer *pb = data;
+       struct afs_callback_arg *aca = data;
        char *mood_name, *errmsg;
        struct osl_object mood_def;
-       struct mood *m;
+       struct mood_instance *m;
        int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
  
        if (ret < 0) {
-               para_printf(pb, "cannot read mood\n");
+               afs_error(aca, "cannot read mood\n");
                return ret;
        }
        if (!*mood_name) /* ignore dummy row */
        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);
+               afs_error(aca, "%s: %s\n%s\n", mood_name, errmsg,
+                       para_strerror(-ret));
                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 */
@@@ -200,7 -204,7 +204,7 @@@ out
  /**
   * Check all moods for syntax errors.
   *
-  * \param aca Only ->pbout is used for diagnostics.
+  * \param aca Output goes to ->pbout, errors to ->fd on the error band.
   *
   * \return Negative on fatal errors. Inconsistent mood definitions are not
   * considered an error.
  int mood_check_callback(struct afs_callback_arg *aca)
  {
        para_printf(&aca->pbout, "checking moods...\n");
-       return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
-               check_mood));
+       return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, aca, check_mood));
  }
  
  /*
   * overflows and rounding errors we store the common divisor of the
   * correction factors separately.
   */
- static long compute_score(struct afs_info *afsi)
 -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,
+               const struct afs_statistics *stats)
  {
 -      long score = -normalized_value(afsi->num_played, stats->num,
 -              stats->num_played_sum, stats->num_played_qd);
 -      score -= normalized_value(afsi->last_played, stats->num,
 -              stats->last_played_sum, stats->last_played_qd);
 -      return score / 2;
 +      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;
++      assert(stats->normalization_divisor > 0);
++      assert(stats->num > 0);
++      mean_n = stats->num_played_sum / stats->num;
++      mean_l = stats->last_played_sum / stats->num;
 +
 +      score_n = -((int64_t)afsi->num_played - mean_n)
-               * statistics.num_played_correction
-               / statistics.normalization_divisor;
++              * stats->num_played_correction
++              / stats->normalization_divisor;
 +      score_l = -((int64_t)afsi->last_played - mean_l)
-               * statistics.last_played_correction
-               / statistics.normalization_divisor;
++              * stats->last_played_correction
++              / stats->normalization_divisor;
 +      return (score_n + score_l) / 2;
  }
  
- static int add_afs_statistics(const struct osl_row *row)
+ static int add_afs_statistics(const struct osl_row *row,
+               struct afs_statistics *stats)
  {
        uint64_t n, x, s, q;
        struct afs_info afsi;
        ret = get_afsi_of_row(row, &afsi);
        if (ret < 0)
                return ret;
-       n = statistics.num;
+       n = stats->num;
        x = afsi.last_played;
-       s = statistics.last_played_sum;
+       s = stats->last_played_sum;
        if (n > 0) {
                q = (x > s / n)? x - s / n : s / n - x;
-               statistics.last_played_qd += q * q * n / (n + 1);
+               stats->last_played_qd += q * q * n / (n + 1);
        }
-       statistics.last_played_sum += x;
+       stats->last_played_sum += x;
  
        x = afsi.num_played;
-       s = statistics.num_played_sum;
+       s = stats->num_played_sum;
        if (n > 0) {
                q = (x > s / n)? x - s / n : s / n - x;
-               statistics.num_played_qd += q * q * n / (n + 1);
+               stats->num_played_qd += q * q * n / (n + 1);
        }
-       statistics.num_played_sum += x;
-       statistics.num++;
+       stats->num_played_sum += x;
+       stats->num++;
        return 1;
  }
  
  static int del_afs_statistics(const struct osl_row *row)
  {
+       struct afs_statistics *stats = &current_mood->stats;
        uint64_t n, s, q, a, new_s;
        struct afs_info afsi;
        int ret;
        ret = get_afsi_of_row(row, &afsi);
        if (ret < 0)
                return ret;
-       n = statistics.num;
+       n = stats->num;
        assert(n);
        if (n == 1) {
-               memset(&statistics, 0, sizeof(statistics));
-               statistics.normalization_divisor = 1;
+               memset(stats, 0, sizeof(*stats));
+               stats->normalization_divisor = 1;
                return 1;
        }
  
-       s = statistics.last_played_sum;
-       q = statistics.last_played_qd;
+       s = stats->last_played_sum;
+       q = stats->last_played_qd;
        a = afsi.last_played;
        new_s = s - a;
-       statistics.last_played_sum = new_s;
-       statistics.last_played_qd = q + s * s / n - a * a
+       stats->last_played_sum = new_s;
+       stats->last_played_qd = q + s * s / n - a * a
                - new_s * new_s / (n - 1);
  
-       s = statistics.num_played_sum;
-       q = statistics.num_played_qd;
+       s = stats->num_played_sum;
+       q = stats->num_played_qd;
        a = afsi.num_played;
        new_s = s - a;
-       statistics.num_played_sum = new_s;
-       statistics.num_played_qd = q + s * s / n - a * a
+       stats->num_played_sum = new_s;
+       stats->num_played_qd = q + s * s / n - a * a
                - new_s * new_s / (n - 1);
  
-       statistics.num--;
+       stats->num--;
        return 1;
  }
  
  /*
-  * At mood open time we determine the set of admissible files for the given
+  * At mood load time we determine the set of admissible files for the given
   * 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
   */
  struct admissible_array {
        /** Files are admissible wrt. this mood. */
-       struct mood *m;
+       struct mood_instance *m;
        /** The size of the array */
        unsigned size;
        /** Pointer to the array of admissible files. */
  static int add_if_admissible(struct osl_row *aft_row, void *data)
  {
        struct admissible_array *aa = data;
-       int ret;
+       struct afs_statistics *stats = &aa->m->stats;
  
-       ret = row_is_admissible(aft_row, aa->m);
-       if (ret <= 0)
-               return ret;
-       if (statistics.num >= aa->size) {
+       if (!mp_eval_row(aft_row, aa->m->parser_context))
+               return 0;
+       if (stats->num >= aa->size) {
                aa->size *= 2;
                aa->size += 100;
                aa->array = arr_realloc(aa->array, aa->size,
                        sizeof(struct osl_row *));
        }
-       aa->array[statistics.num] = aft_row;
-       return add_afs_statistics(aft_row);
+       aa->array[stats->num] = aft_row;
+       return add_afs_statistics(aft_row, stats);
  }
  
  /**
@@@ -414,29 -417,25 +419,25 @@@ _static_inline_ int64_t update_quadrati
        return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
  }
  
- static int update_afs_statistics(struct afs_info *old_afsi,
+ static void update_afs_statistics(struct afs_info *old_afsi,
                struct afs_info *new_afsi)
  {
-       unsigned n;
-       int ret = get_num_admissible_files(&n);
-       if (ret < 0)
-               return ret;
-       assert(n);
-       statistics.last_played_qd = update_quadratic_deviation(n,
-               statistics.last_played_qd, old_afsi->last_played,
-               new_afsi->last_played, statistics.last_played_sum);
-       statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
-       statistics.num_played_qd = update_quadratic_deviation(n,
-               statistics.num_played_qd, old_afsi->num_played,
-               new_afsi->num_played, statistics.num_played_sum);
-       statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
-       return 1;
+       struct afs_statistics *stats = &current_mood->stats;
+       assert(stats->num > 0);
+       stats->last_played_qd = update_quadratic_deviation(stats->num,
+               stats->last_played_qd, old_afsi->last_played,
+               new_afsi->last_played, stats->last_played_sum);
+       stats->last_played_sum += new_afsi->last_played - old_afsi->last_played;
+       stats->num_played_qd = update_quadratic_deviation(stats->num,
+               stats->num_played_qd, old_afsi->num_played,
+               new_afsi->num_played, stats->num_played_sum);
+       stats->num_played_sum += new_afsi->num_played - old_afsi->num_played;
  }
  
- static int add_to_score_table(const struct osl_row *aft_row)
+ static int add_to_score_table(const struct osl_row *aft_row,
+               const struct afs_statistics *stats)
  {
        long score;
        struct afs_info afsi;
  
        if (ret < 0)
                return ret;
-       score = compute_score(&afsi);
+       score = compute_score(&afsi, stats);
        return score_add(aft_row, score);
  }
  
@@@ -457,23 -456,18 +458,18 @@@ static int delete_from_statistics_and_s
  }
  
  /**
-  * Delete one entry from the statistics and from the score table.
+  * Delete an audio file from the score table and update mood statistics.
   *
-  * \param aft_row The audio file which is no longer admissible.
+  * \param aft_row Identifies the row to delete.
   *
-  * \return Positive on success, negative on errors.
+  * \return Standard.
   *
   * \sa \ref score_delete().
   */
  static int mood_delete_audio_file(const struct osl_row *aft_row)
  {
-       int ret;
-       ret = row_belongs_to_score_table(aft_row, NULL);
-       if (ret < 0)
-               return ret;
-       if (!ret) /* not admissible, nothing to do */
-               return 1;
+       if (!row_belongs_to_score_table(aft_row))
+               return 0;
        return delete_from_statistics_and_score_table(aft_row);
  }
  
@@@ -492,95 -486,71 +488,71 @@@ static int mood_update_audio_file(cons
                struct afs_info *old_afsi)
  {
        long score, percent;
-       int ret, is_admissible, was_admissible = 0;
+       int ret;
+       bool is_admissible, was_admissible;
        struct afs_info afsi;
-       unsigned rank;
  
        if (!current_mood)
                return 1; /* nothing to do */
-       ret = row_belongs_to_score_table(aft_row, &rank);
-       if (ret < 0)
-               return ret;
-       was_admissible = ret;
-       ret = row_is_admissible(aft_row, current_mood);
-       if (ret < 0)
-               return ret;
-       is_admissible = (ret > 0);
+       was_admissible = row_belongs_to_score_table(aft_row);
+       is_admissible = mp_eval_row(aft_row, current_mood->parser_context);
        if (!was_admissible && !is_admissible)
                return 1;
        if (was_admissible && !is_admissible)
                return delete_from_statistics_and_score_table(aft_row);
        if (!was_admissible && is_admissible) {
-               ret = add_afs_statistics(aft_row);
+               ret = add_afs_statistics(aft_row, &current_mood->stats);
                if (ret < 0)
                        return ret;
-               return add_to_score_table(aft_row);
+               return add_to_score_table(aft_row, &current_mood->stats);
        }
        /* update score */
        ret = get_afsi_of_row(aft_row, &afsi);
        if (ret < 0)
                return ret;
-       if (old_afsi) {
-               ret = update_afs_statistics(old_afsi, &afsi);
-               if (ret < 0)
-                       return ret;
-       }
-       score = compute_score(&afsi);
+       if (old_afsi)
+               update_afs_statistics(old_afsi, &afsi);
+       score = compute_score(&afsi, &current_mood->stats);
        PARA_DEBUG_LOG("score: %li\n", score);
        percent = (score + 100) / 3;
        if (percent > 100)
                percent = 100;
        else if (percent < 0)
                percent = 0;
-       PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
+       PARA_DEBUG_LOG("moving to %li%%\n", percent);
        return score_update(aft_row, percent);
  }
  
  /* sse: seconds since epoch. */
- static void log_statistics(int64_t sse)
+ static char *get_statistics(struct mood_instance *m, int64_t sse)
  {
-       unsigned n = statistics.num;
+       unsigned n = m->stats.num;
        int mean_days, sigma_days;
  
-       assert(current_mood);
-       PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
-               current_mood->name : "(dummy)");
-       if (!n) {
-               PARA_WARNING_LOG("no admissible files\n");
-               return;
-       }
-       PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
-       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: %" 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);
+       mean_days = (sse - m->stats.last_played_sum / n) / 3600 / 24;
+       sigma_days = int_sqrt(m->stats.last_played_qd / n) / 3600 / 24;
+       return make_message(
+               "loaded mood %s (%u files)\n"
+               "last_played mean/sigma: %d/%d days\n"
+               "num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n"
+       ,
+               m->name? m->name : "(dummy)",
+               n,
+               mean_days, sigma_days,
+               m->stats.num_played_sum / n,
+                       int_sqrt(m->stats.num_played_qd / n)
+       );
  }
  
- /**
-  * Close the current mood.
-  *
-  * Frees all resources of the current mood.
-  */
- void close_current_mood(void)
+ /** Free all resources of the current mood, if any. */
+ void mood_unload(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)
+ static void compute_correction_factors(int64_t sse, struct afs_statistics *s)
  {
-       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;
  /**
   * Change the current mood.
   *
-  * \param mood_name The name of the mood to open.
-  * \param errmsg Error description is returned here.
+  * \param mood_name The name of the mood to load.
+  * \param msg Error message or mood info is returned here.
   *
   * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
   * and uses a scoring method based only on the \a last_played information.
   *
-  * The errmsg pointer may be NULL, in which case no error message will be
-  * returned. If a non-NULL pointer is given, the caller must free *errmsg.
-  *
-  * If there is already an open mood, it will be closed first.
+  * If the message pointer is not NULL, a suitable message is returned there in
+  * all cases. The caller must free this string.
   *
-  * \return Positive on success, negative on errors.
+  * \return The number of admissible files on success, negative on errors. It is
+  * not considered an error if no files are admissible.
   *
   * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
   */
- int change_current_mood(const char *mood_name, char **errmsg)
+ int mood_load(const char *mood_name, char **msg)
  {
        int i, ret;
-       struct admissible_array aa = {
-               .size = 0,
-               .array = NULL
-       };
+       struct admissible_array aa = {.size = 0};
        /*
         * 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;
-               struct osl_row *row;
-               struct osl_object obj;
-               if (!*mood_name) {
-                       *errmsg = make_message("empty mood name");
-                       return -ERRNO_TO_PARA_ERROR(EINVAL);
-               }
-               obj.data = (char *)mood_name;
-               obj.size = strlen(mood_name) + 1;
-               ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
-               if (ret < 0) {
-                       if (errmsg)
-                               *errmsg = make_message("no such mood: %s",
-                                       mood_name);
-                       return ret;
-               }
-               ret = load_mood(row, &m, errmsg);
+               ret = init_mood_parser(mood_name, &aa.m, msg);
                if (ret < 0)
                        return ret;
-               close_current_mood();
-               current_mood = m;
-       } else { /* load dummy mood */
-               close_current_mood();
-               current_mood = alloc_new_mood(NULL);
-       }
-       aa.m = current_mood;
+       } else /* load dummy mood */
+               aa.m = alloc_new_mood(NULL);
        PARA_NOTICE_LOG("computing statistics of admissible files\n");
        ret = audio_file_loop(&aa, add_if_admissible);
        if (ret < 0) {
-               if (errmsg)
-                       *errmsg = make_message("audio file loop failed");
+               if (msg) /* false if we are called via the event handler */
+                       *msg = make_message("audio file loop failed\n");
                goto out;
        }
        clock_get_realtime(&rnow);
-       compute_correction_factors(rnow.tv_sec);
-       log_statistics(rnow.tv_sec);
-       for (i = 0; i < statistics.num; i++) {
-               ret = add_to_score_table(aa.array[i]);
+       compute_correction_factors(rnow.tv_sec, &aa.m->stats);
+       if (aa.m->stats.num == 0) {
+               if (msg)
+                       *msg = make_message("no admissible files\n");
+               ret = 0;
+               goto out;
+       }
+       for (i = 0; i < aa.m->stats.num; i++) {
+               ret = add_to_score_table(aa.array[i], &aa.m->stats);
                if (ret < 0) {
-                       if (errmsg)
-                               *errmsg = make_message(
-                                       "could not add row to score table");
+                       if (msg)
+                               *msg = make_message(
+                                       "could not add row to score table\n");
                        goto out;
                }
        }
-       ret = statistics.num;
+       /* success */
+       if (msg)
+               *msg = get_statistics(aa.m, rnow.tv_sec);
+       ret = aa.m->stats.num;
+       mood_unload();
+       current_mood = aa.m;
  out:
        free(aa.array);
        if (ret < 0)
-               close_current_mood();
+               destroy_mood(aa.m);
        return ret;
  }
  
  /*
-  * Close and re-open the current mood.
+  * Empty the score table and start over.
   *
   * This function is called on events which render the current list of
   * admissible files useless, for example if an attribute is removed from the
   * attribute table.
-  *
-  * If no mood is currently open, the function returns success.
   */
  static int reload_current_mood(void)
  {
        int ret;
        char *mood_name = NULL;
  
-       ret = clear_score_table();
-       if (ret < 0)
-               return ret;
-       if (!current_mood)
-               return 1;
+       assert(current_mood);
+       score_clear();
        PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
                current_mood->name : "(dummy)");
        if (current_mood->name)
                mood_name = para_strdup(current_mood->name);
-       close_current_mood();
-       ret = change_current_mood(mood_name, NULL);
+       mood_unload();
+       ret = mood_load(mood_name, NULL);
        free(mood_name);
        return ret;
  }