X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=mood.c;h=bf3f39fa26934fd832b5847f034fdd151602145b;hb=6302a94ab153c9cc955b5fdff24422a9de3a1dcf;hp=40228be515678effb04587f61d0fbc75acbc44c1;hpb=fe3d9cd155b5eac8706015854c343440823e12da;p=paraslash.git diff --git a/mood.c b/mood.c index 40228be5..bf3f39fa 100644 --- a/mood.c +++ b/mood.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 2007 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2007 Andre Noll , see file COPYING. */ /** \file mood.c Paraslash's mood handling functions. */ @@ -19,6 +15,16 @@ #include "mm.h" #include "mood.h" +/* + * Mood parser API. It's overkill to have an own header file for + * these declarations as they are only needed in this .c file. + */ +struct mp_context; +int mp_init(const char *definition, int nbytes, struct mp_context **result, + char **errmsg); +bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx); +void mp_shutdown(struct mp_context *ctx); + /** * Contains statistical data of the currently admissible audio files. * @@ -33,10 +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. @@ -73,6 +85,8 @@ struct mood { 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. */ + struct mp_context *parser_context; }; /* @@ -81,32 +95,60 @@ struct mood { */ static struct mood *current_mood; -/** - * Rough approximation to sqrt. +/* + * Find the position of the most-significant set bit. * - * \param x Integer of which to calculate the sqrt. + * Copied and slightly adapted from the linux source tree, version 4.9.39 + * (2017-07). + */ +__a_const static uint32_t fls64(uint64_t v) +{ + int n = 63; + const uint64_t ones = ~(uint64_t)0U; + + if ((v & (ones << 32)) == 0) { + n -= 32; + v <<= 32; + } + if ((v & (ones << (64 - 16))) == 0) { + n -= 16; + v <<= 16; + } + if ((v & (ones << (64 - 8))) == 0) { + n -= 8; + v <<= 8; + } + if ((v & (ones << (64 - 4))) == 0) { + n -= 4; + v <<= 4; + } + if ((v & (ones << (64 - 2))) == 0) { + n -= 2; + v <<= 2; + } + if ((v & (ones << (64 - 1))) == 0) + n -= 1; + return n; +} + +/* + * Compute the integer square root floor(sqrt(x)). * - * \return An integer res with res * res <= x. + * Taken 2007 from the linux source tree. */ __a_const static uint64_t int_sqrt(uint64_t x) { - uint64_t op, res, one = 1; - op = x; - res = 0; - - one = one << 62; - while (one > op) - one >>= 2; + uint64_t op = x, res = 0, one = 1; + one = one << (fls64(x) & ~one); while (one != 0) { if (op >= res + one) { op = op - (res + one); - res = res + 2 * one; + res = res + 2 * one; } res /= 2; one /= 4; } -// PARA_NOTICE_LOG("sqrt(%llu) = %llu\n", x, res); return res; } @@ -150,6 +192,10 @@ static int row_is_admissible(const struct osl_row *aft_row, struct mood *m, 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; @@ -214,6 +260,7 @@ static void destroy_mood(struct mood *m) 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); } @@ -222,9 +269,9 @@ static struct mood *alloc_new_mood(const char *name) 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; } @@ -367,7 +414,8 @@ out: return ret; } -static int load_mood(const struct osl_row *mood_row, struct mood **m) +static int load_mood(const struct osl_row *mood_row, struct mood **m, + char **errmsg) { char *mood_name; struct osl_object mood_def; @@ -376,22 +424,31 @@ static int load_mood(const struct osl_row *mood_row, struct mood **m) *m = NULL; ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def); - if (ret < 0) + if (ret < 0) { + if (errmsg) + *errmsg = make_message( + "could not read mood definition"); return ret; - if (!*mood_name) - return -E_DUMMY_ROW; + } + 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); - osl_close_disk_object(&mood_def); if (ret < 0) { - PARA_ERROR_LOG("unable to load mood %s: %s\n", mlpd.m->name, - para_strerror(-ret)); - destroy_mood(mlpd.m); - return ret; + 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 = mlpd.m; - return 1; + 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) @@ -409,12 +466,24 @@ static int check_mood(struct osl_row *mood_row, void *data) } if (!*mood_name) /* ignore dummy row */ goto out; - para_printf(pb, "checking mood %s...\n", mood_name); ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size, parse_mood_line, &mlpd); - if (ret < 0) - para_printf(pb, "mood %s: error in line %u: %s\n", mood_name, - mlpd.line_num, para_strerror(-ret)); + 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); + + } ret = 1; /* don't fail the loop on invalid mood definitions */ out: osl_close_disk_object(&mood_def); @@ -436,20 +505,59 @@ int mood_check_callback(struct afs_callback_arg *aca) 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) @@ -493,6 +601,7 @@ static int del_afs_statistics(const struct osl_row *row) assert(n); if (n == 1) { memset(&statistics, 0, sizeof(statistics)); + statistics.normalization_divisor = 1; return 1; } @@ -741,15 +850,11 @@ static int mood_update_audio_file(const struct osl_row *aft_row, 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? @@ -759,13 +864,18 @@ static void log_statistics(void) 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); } /** @@ -778,30 +888,58 @@ 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; } /** * Change the current mood. * * \param mood_name The name of the mood to open. + * \param errmsg Error description 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. * * \return Positive on success, negative on errors. Loading the dummy mood * always succeeds. * - * \sa struct \ref afs_info::last_played. + * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). */ -int change_current_mood(const char *mood_name) +int change_current_mood(const char *mood_name, char **errmsg) { int i, ret; struct admissible_array aa = { .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; @@ -812,10 +950,12 @@ int change_current_mood(const char *mood_name) }; ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) { - PARA_NOTICE_LOG("no such mood: %s\n", mood_name); + if (errmsg) + *errmsg = make_message("no such mood: %s", + mood_name); return ret; } - ret = load_mood(row, &m); + ret = load_mood(row, &m, errmsg); if (ret < 0) return ret; close_current_mood(); @@ -827,15 +967,24 @@ int change_current_mood(const char *mood_name) aa.m = current_mood; PARA_NOTICE_LOG("computing statistics of admissible files\n"); ret = audio_file_loop(&aa, add_if_admissible); - if (ret < 0) + if (ret < 0) { + if (errmsg) + *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); - if (ret < 0) + if (ret < 0) { + if (errmsg) + *errmsg = make_message( + "could not add row to score table"); goto out; + } } - log_statistics(); ret = statistics.num; out: free(aa.array); @@ -866,7 +1015,7 @@ static int reload_current_mood(void) if (current_mood->name) mood_name = para_strdup(current_mood->name); close_current_mood(); - ret = change_current_mood(mood_name); + ret = change_current_mood(mood_name, NULL); free(mood_name); return ret; }