X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=mood.c;h=50a67793ea6c6ccaeceb4298b0beb028c8926286;hb=9055c71be97f1095dcdbd83da305b600f204f763;hp=5b0ddfdd7449cd89e1e32c51565b5012f1597e69;hpb=5ae1a5bb9732d777f856d5bb003585f81e3c519a;p=paraslash.git diff --git a/mood.c b/mood.c index 5b0ddfdd..50a67793 100644 --- a/mood.c +++ b/mood.c @@ -12,7 +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 @@ -49,11 +48,11 @@ struct afs_statistics { }; /** - * Stores an instance of an open mood (parser and statistics). + * Stores an instance of a loaded mood (parser and statistics). * - * A structure of this type is allocated and initialized at mood open time. + * A structure of this type is allocated and initialized when a mood is loaded. */ -struct mood { +struct mood_instance { /** NULL means that this is the "dummy" mood. */ char *name; /** Bison's abstract syntax tree, used to determine admissibility. */ @@ -63,13 +62,13 @@ struct mood { }; /* - * 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. @@ -128,7 +127,7 @@ __a_const static uint64_t int_sqrt(uint64_t x) return res; } -static void destroy_mood(struct mood *m) +static void destroy_mood(struct mood_instance *m) { if (!m) return; @@ -137,16 +136,18 @@ static void destroy_mood(struct mood *m) 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 init_mood_parser(const char *mood_name, struct mood **m, char **err) +static int init_mood_parser(const char *mood_name, struct mood_instance **m, + char **err) { struct osl_object mood_def; int ret; @@ -163,7 +164,7 @@ static int init_mood_parser(const char *mood_name, struct mood **m, char **err) return ret; } *m = alloc_new_mood(mood_name); - PARA_INFO_LOG("opening mood %s\n", mood_name); + 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) @@ -176,7 +177,7 @@ static int check_mood(struct osl_row *mood_row, void *data) struct para_buffer *pb = 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) { @@ -259,9 +260,9 @@ static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd) return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd); } -static long compute_score(struct afs_info *afsi) +static long compute_score(struct afs_info *afsi, + const struct afs_statistics *stats) { - const struct afs_statistics *stats = ¤t_mood->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, @@ -269,12 +270,12 @@ static long compute_score(struct afs_info *afsi) return score / 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; int ret; - struct afs_statistics *stats = ¤t_mood->stats; ret = get_afsi_of_row(row, &afsi); if (ret < 0) @@ -337,7 +338,7 @@ static int del_afs_statistics(const struct osl_row *row) } /* - * 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 @@ -347,7 +348,7 @@ static int del_afs_statistics(const struct osl_row *row) */ 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. */ @@ -360,8 +361,8 @@ struct admissible_array { */ static int add_if_admissible(struct osl_row *aft_row, void *data) { - const struct afs_statistics *stats = ¤t_mood->stats; struct admissible_array *aa = data; + struct afs_statistics *stats = &aa->m->stats; if (!mp_eval_row(aft_row, aa->m->parser_context)) return 0; @@ -372,7 +373,7 @@ static int add_if_admissible(struct osl_row *aft_row, void *data) sizeof(struct osl_row *)); } aa->array[stats->num] = aft_row; - return add_afs_statistics(aft_row); + return add_afs_statistics(aft_row, stats); } /** @@ -434,7 +435,8 @@ static void update_afs_statistics(struct afs_info *old_afsi, 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; @@ -442,7 +444,7 @@ static int add_to_score_table(const struct osl_row *aft_row) if (ret < 0) return ret; - score = compute_score(&afsi); + score = compute_score(&afsi, stats); return score_add(aft_row, score); } @@ -455,23 +457,18 @@ static int delete_from_statistics_and_score_table(const struct osl_row *aft_row) } /** - * 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); } @@ -493,24 +490,20 @@ static int mood_update_audio_file(const struct osl_row *aft_row, 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; + 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, ¤t_mood->stats); if (ret < 0) return ret; - return add_to_score_table(aft_row); + return add_to_score_table(aft_row, ¤t_mood->stats); } /* update score */ ret = get_afsi_of_row(aft_row, &afsi); @@ -518,61 +511,47 @@ static int mood_update_audio_file(const struct osl_row *aft_row, return ret; if (old_afsi) update_afs_statistics(old_afsi, &afsi); - score = compute_score(&afsi); + score = compute_score(&afsi, ¤t_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) { - const struct afs_statistics *stats = ¤t_mood->stats; - unsigned n = stats->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", stats->num); - mean_days = (sse - stats->last_played_sum / n) / 3600 / 24; - sigma_days = int_sqrt(stats->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", - stats->num_played_sum / n, - int_sqrt(stats->num_played_qd / n)); - PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n", - stats->num_played_correction); - PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n", - stats->last_played_correction); - PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n", - stats->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; } -static void compute_correction_factors(int64_t sse) +static void compute_correction_factors(int64_t sse, struct afs_statistics *s) { - struct afs_statistics *s = ¤t_mood->stats; - if (s->num > 0) { s->normalization_divisor = int_sqrt(s->last_played_qd) * int_sqrt(s->num_played_qd) / s->num / 100; @@ -590,28 +569,24 @@ static void compute_correction_factors(int64_t sse) /** * 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 the message pointer is not NULL, a suitable message is returned there in + * all cases. The caller must free this string. * - * If there is already an open mood, it will be closed first. - * - * \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". @@ -619,46 +594,50 @@ int change_current_mood(const char *mood_name, char **errmsg) struct timeval rnow; if (mood_name) { - struct mood *m; - ret = init_mood_parser(mood_name, &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 < current_mood->stats.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 = current_mood->stats.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 @@ -670,15 +649,13 @@ static int reload_current_mood(void) char *mood_name = NULL; assert(current_mood); - ret = clear_score_table(); - if (ret < 0) - return ret; + 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; }