X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=mood.c;h=a228b2432f9b56d3bc51535c13d74094314e7da9;hp=ccc57a0305d5e23152d789bb974b53729171ec25;hb=c20e3e67fc2be1d5f8778b9c482271e023bbdcb8;hpb=184ea897a9b446611a9d02315160b6d77c0926fe diff --git a/mood.c b/mood.c index ccc57a03..a228b243 100644 --- a/mood.c +++ b/mood.c @@ -38,10 +38,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}; struct mood { /** The name of this mood. */ @@ -157,6 +163,8 @@ static int load_mood(const struct osl_row *mood_row, struct mood **m, 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) + destroy_mood(*m); return ret; } @@ -204,6 +212,43 @@ int mood_check_callback(struct afs_callback_arg *aca) check_mood)); } +/* + * 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) @@ -261,6 +306,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; } @@ -488,15 +534,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? @@ -506,13 +548,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); } /** @@ -525,6 +572,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; } /** @@ -541,8 +607,7 @@ void close_current_mood(void) * * 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. + * \return Positive on success, negative on errors. * * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). */ @@ -553,14 +618,23 @@ int change_current_mood(const char *mood_name, char **errmsg) .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; struct osl_row *row; - struct osl_object obj = { - .data = (char *)mood_name, - .size = strlen(mood_name) + 1 - }; + 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) @@ -583,8 +657,11 @@ int change_current_mood(const char *mood_name, char **errmsg) if (ret < 0) { if (errmsg) *errmsg = make_message("audio file loop failed"); - return ret; + 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]); if (ret < 0) { @@ -594,10 +671,11 @@ int change_current_mood(const char *mood_name, char **errmsg) goto out; } } - log_statistics(); ret = statistics.num; out: free(aa.array); + if (ret < 0) + close_current_mood(); return ret; }