crypto_ldflags := @crypto_ldflags@
iconv_ldflags := @iconv_ldflags@
+ENABLE_UBSAN := @ENABLE_UBSAN@
+
include Makefile.real
uname_rs := $(shell uname -rs)
cc_version := $(shell $(CC) --version | head -n 1)
GIT_VERSION := $(shell ./GIT-VERSION-GEN git-version.h)
-COPYRIGHT_YEAR := 2021
+COPYRIGHT_YEAR := 2022
ifeq ("$(origin O)", "command line")
build_dir := $(O)
STRICT_CFLAGS += -Wdeclaration-after-statement
STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute
+ifeq ($(ENABLE_UBSAN), yes)
+ STRICT_CFLAGS += -fsanitize=undefined
+ LDFLAGS += -lubsan
+endif
+
ifeq ($(uname_s),Linux)
# these cause warnings on *BSD
CPPFLAGS += -Wunused-macros
NEWS
====
+---------------------------------------
+0.7.0 (to be announced) "seismic orbit"
+---------------------------------------
+
+- Starting with paraslash-0.7.0, the sha256 hash value of each known
+ audio file is stored in the database while older versions employed the
+ sha1 hash algorithm which has been considered insecure since 2005
+ and should no longer be used today. The switch from sha1 to sha256
+ requires users to upgrade their database using the new para_upgrade_db
+ program, followed by re-adding all files to recompute the hashes. With
+ this approach all metadata stored in the database (last played date,
+ num played value, moods, playlists, attributes etc.) are kept. An
+ simpler alternative is to start over from scratch by running the
+ "init" command but this will lose these metadata.
+- Server and client now hash the session keys with sha256 rather
+ than sha1 during the initial handshake. This feature is optional and
+ backwards compatible: old clients can still connect to a new server
+ (using sha1). Also new clients can connect to an old server (again
+ using sha1).
+- 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"
-----------------------------------------
int i, ret;
register_signal_task(&s);
- INIT_LIST_HEAD(&afs_client_list);
+ init_list_head(&afs_client_list);
for (i = 0; i < NUM_AFS_TABLES; i++)
afs_tables[i].init(&afs_tables[i]);
ret = open_afs_tables();
if (!pad)
return;
+ if (!pad->handle)
+ goto free_pad;
/*
* It's OK to have a blocking operation here because we already made
* sure that the PCM output buffer is (nearly) empty.
snd_pcm_drain(pad->handle);
snd_pcm_close(pad->handle);
snd_config_update_free_global();
+free_pad:
free(pad);
}
if (bytes == 0) /* no data available */
return 0;
pad = wn->private_data = para_calloc(sizeof(*pad));
- get_btr_sample_rate(btrn, &val);
+ ret = get_btr_sample_rate(btrn, &val);
+ if (ret < 0)
+ goto err;
pad->sample_rate = val;
- get_btr_channels(btrn, &val);
+ ret = get_btr_channels(btrn, &val);
+ if (ret < 0)
+ goto err;
pad->channels = val;
- get_btr_sample_format(btrn, &val);
+ ret = get_btr_sample_format(btrn, &val);
+ if (ret < 0)
+ goto err;
pad->sample_format = get_alsa_pcm_format(val);
-
PARA_INFO_LOG("%u channel(s), %uHz\n", pad->channels,
pad->sample_rate);
ret = alsa_init(wn);
- if (ret < 0) {
- free(wn->private_data);
- wn->private_data = NULL;
+ if (ret < 0)
goto err;
- }
wn->min_iqs = pad->bytes_per_frame;
goto again;
}
goto again;
}
if (frames == -EPIPE) {
- PARA_WARNING_LOG("underrun (tried to write %zu bytes)\n", bytes);
+ snd_pcm_status_t *status;
+ snd_pcm_status_malloc(&status);
+ if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN)
+ PARA_WARNING_LOG("underrun\n");
+ snd_pcm_status_free(status);
snd_pcm_prepare(pad->handle);
return 0;
}
goto remove_btrn;
if (ret == 0)
return 0;
- get_btr_sample_rate(wn->btrn, &rate);
- get_btr_channels(wn->btrn, &ch);
- get_btr_sample_format(wn->btrn, &format);
+ ret = get_btr_sample_rate(wn->btrn, &rate);
+ if (ret < 0)
+ goto remove_btrn;
+ ret = get_btr_channels(wn->btrn, &ch);
+ if (ret < 0)
+ goto remove_btrn;
+ ret = get_btr_sample_format(wn->btrn, &format);
+ if (ret < 0)
+ goto remove_btrn;
ret = aow_init(wn, rate, ch, format);
if (ret < 0)
goto remove_btrn;
task_reap(&s->receiver_node->task);
free(s->receiver_node);
s->receiver_node = NULL;
- stat_task->current_audio_format_num = -1;
+ if (audiod_status == AUDIOD_ON)
+ stat_task->current_audio_format_num = -1;
tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000),
&a->restart_barrier);
}
btrn->context = bnd->context;
btrn->start.tv_sec = 0;
btrn->start.tv_usec = 0;
- INIT_LIST_HEAD(&btrn->children);
- INIT_LIST_HEAD(&btrn->input_queue);
+ init_list_head(&btrn->children);
+ init_list_head(&btrn->input_queue);
if (!bnd->child) {
if (bnd->parent) {
list_add_tail(&btrn->node, &bnd->parent->children);
struct chunk_queue *cq_new(size_t max_pending)
{
struct chunk_queue *cq = para_malloc(sizeof(*cq));
- INIT_LIST_HEAD(&cq->q);
+ init_list_head(&cq->q);
cq->max_pending = max_pending;
cq->num_pending = 0;
return cq;
I9E_DUMMY_COMPLETER(stop);
I9E_DUMMY_COMPLETER(addatt);
I9E_DUMMY_COMPLETER(init);
-I9E_DUMMY_COMPLETER(tasks);
static struct i9e_completer completers[];
struct close_on_fork *cof = para_malloc(sizeof(struct close_on_fork));
if (!initialized) {
- INIT_LIST_HEAD(&close_on_fork_list);
+ init_list_head(&close_on_fork_list);
initialized = 1;
}
cof->fd = fd;
#include <sys/socket.h>
#include <regex.h>
#include <signal.h>
-#include <sys/types.h>
-#include <osl.h>
#include <arpa/inet.h>
-#include <sys/un.h>
#include <netdb.h>
#include <lopsub.h>
#include "command.h"
#include "string.h"
#include "afh.h"
-#include "afs.h"
#include "net.h"
#include "server.h"
#include "list.h"
}
EXPORT_SERVER_CMD_HANDLER(stat);
-const char *aux_info_cb(unsigned cmd_num, bool verbose)
+static const char *aux_info_cb(unsigned cmd_num, bool verbose)
{
static char result[80];
unsigned perms = server_command_perms[cmd_num];
}
EXPORT_SERVER_CMD_HANDLER(jmp);
-/* deprecated, does nothing */
-static int com_tasks(__a_unused struct command_context *cc,
- __a_unused struct lls_parse_result *lpr)
-{
- return 1;
-}
-EXPORT_SERVER_CMD_HANDLER(tasks);
-
static void reset_signals(void)
{
para_sigaction(SIGCHLD, SIG_IGN);
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
}
/**
- * Set the color for one loglevel.
+ * Set the color for log messages of the given severity level.
*
- * \param arg Must be of the form "ll:[fg [bg]] [attr]".
+ * \param arg Must be of the form "severity:[fg [bg]] [attr]".
*/
void daemon_set_log_color_or_die(const char *arg)
{
PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \
PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \
PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
- PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \
PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \
PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
* 'E_') and gets later redefined to expand to the error text only
*/
#define PARA_ERROR(err, msg) E_ ## err
+/**
+ * Numeric error codes.
+ *
+ * Public functions which can fail return the negated value of one of the
+ * constants defined here to indicate the cause of the error.
+ *
+ * \sa \ref para_strerror().
+ */
enum para_error_codes {PARA_ERRORS};
#undef PARA_ERROR
#define PARA_ERROR(err, msg) msg
* running.
*
* \return A negative return value of zero means the i9e task terminated. Only
- * in this case it is safe to call ie9_close().
+ * in this case it is safe to call i9e_close().
*/
int i9e_get_error(void)
{
* the given text. If the length of this text exceeds the width of the
* terminal, the text is shortened by leaving out a part in the middle.
*/
-void ie9_print_status_bar(char *buf, unsigned len)
+void i9e_print_status_bar(char *buf, unsigned len)
{
size_t x = i9ep->num_columns, y = (x - 4) / 2;
int i9e_open(struct i9e_client_info *ici, struct sched *s);
void i9e_attach_to_stdout(struct btr_node *producer);
-void ie9_print_status_bar(char *buf, unsigned len);
+void i9e_print_status_bar(char *buf, unsigned len);
void i9e_close(void);
void i9e_signal_dispatch(int sig_num);
__printf_2_3 void i9e_log(int ll, const char* fmt,...);
* Copied from the Linux kernel source tree, version 2.6.13.
*
* Licensed under the GPL v2 as per the whole kernel source tree.
- *
*/
-/** \file list.h doubly linked list implementation */
+/** \file list.h Doubly linked list implementation. */
#include <stddef.h> /* offsetof */
-/** get the struct this entry is embedded in */
+/** Get the struct this entry is embedded in. */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
-/**
- * Non-NULL pointers that will result in page faults under normal
- * circumstances, used to verify that nobody uses non-initialized list entries.
- * Used for poisoning the \a next pointer of struct list_head.
- */
-#define LIST_POISON1 ((void *) 0x00100100)
-/** Non-null pointer, used for poisoning the \a prev pointer of struct
- * list_head
- */
-#define LIST_POISON2 ((void *) 0x00200200)
-
-/** Simple doubly linked list implementation. */
+/** A list head is just a pair of pointers. */
struct list_head {
- /** pointer to the next list entry */
+ /** Pointer to the next list entry. */
struct list_head *next;
- /** pointer to the previous list entry */
+ /** Pointer to the previous list entry. */
struct list_head *prev;
};
/** Define an initialized list head. */
-#define INITIALIZED_LIST_HEAD(name) struct list_head name = { &(name), &(name) }
-
-
-/** must be called before using any other list functions */
-#define INIT_LIST_HEAD(ptr) do { \
- (ptr)->next = (ptr); (ptr)->prev = (ptr); \
-} while (0)
+#define INITIALIZED_LIST_HEAD(name) struct list_head name = {&(name), &(name)}
-
-/*
- * Some of the internal functions ("__xxx") are useful when
- * manipulating whole lists rather than single entries, as
- * sometimes we already know the next/prev entries and we can
- * generate better code by using them directly rather than
- * using the generic single-entry routines.
- */
-
-
-/*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static inline void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next)
+/** This must be called before using any other list functions. */
+static inline void init_list_head(struct list_head *head)
{
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
+ head->next = head;
+ head->prev = head;
}
/**
- * add a new entry
+ * Insert a new entry after the specified head.
*
- * \param new new entry to be added
- * \param head list head to add it after
+ * \param entry The new entry to add.
+ * \param head The list head to add it after.
*
- * Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
-static inline void para_list_add(struct list_head *new, struct list_head *head)
+static inline void para_list_add(struct list_head *entry, struct list_head *head)
{
- __list_add(new, head, head->next);
+ entry->prev = head;
+ entry->next = head->next;
+ head->next->prev = entry;
+ head->next = entry;
}
/**
- * add a new entry
+ * Insert a new entry before the specified head.
*
- * \param new new entry to be added
- * \param head list head to add it before
+ * \param entry The new entry to add.
+ * \param head list head to add it before.
*
- * Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
-static inline void list_add_tail(struct list_head *new, struct list_head *head)
+static inline void list_add_tail(struct list_head *entry, struct list_head *head)
{
- __list_add(new, head->prev, head);
+ entry->prev = head->prev;
+ entry->next = head;
+ head->prev->next = entry;
+ head->prev = entry;
}
-/*
- * Delete a list entry by making the prev/next entries
- * point to each other.
+/**
+ * Delete an entry from a list.
+ *
+ * \param entry The element to delete.
*
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
+ * The list entry is in an undefined state after this and \ref list_empty()
+ * does not return true.
*/
-static inline void __list_del(struct list_head * prev, struct list_head * next)
+static inline void list_del(struct list_head *entry)
{
- next->prev = prev;
- prev->next = next;
+ entry->prev->next = entry->next;
+ entry->next->prev = entry->prev;
+ /*
+ * These non-NULL pointers result in page faults when dereferenced.
+ * This helps to catch bugs resulting from using deleted list heads.
+ */
+ entry->next = (void *)0x00100100;
+ entry->prev = (void *)0x00200200;
}
/**
- * Delete entry from list.
- *
- * \param entry the element to delete from the list.
+ * Delete an entry from one list and add it as another list's head.
*
- * Note: list_empty on entry does not return true after this, the entry is
- * in an undefined state.
+ * \param entry The entry to move.
+ * \param head The head that will precede our entry.
*/
-static inline void list_del(struct list_head *entry)
+static inline void list_move(struct list_head *entry, struct list_head *head)
{
- __list_del(entry->prev, entry->next);
- entry->next = LIST_POISON1;
- entry->prev = LIST_POISON2;
+ list_del(entry);
+ para_list_add(entry, head);
}
/**
- * delete from one list and add as another's head
+ * Test whether a list contains no entries.
*
- * \param list: the entry to move
- * \param head: the head that will precede our entry
+ * \param head The list to test.
*/
-static inline void list_move(struct list_head *list, struct list_head *head)
+static inline int list_empty(const struct list_head *head)
{
- __list_del(list->prev, list->next);
- para_list_add(list, head);
+ return head->next == head;
}
/**
- * test whether a list is empty
+ * Test whether a list has just one entry.
*
- * \param head the list to test.
+ * \param head The list to test.
*/
-static inline int list_empty(const struct list_head *head)
+static inline int list_is_singular(const struct list_head *head)
{
- return head->next == head;
+ return !list_empty(head) && (head->next == head->prev);
}
/**
- * get the struct for this entry
+ * Get the struct in which this entry is embedded in.
*
- * \param ptr the &struct list_head pointer.
- * \param type the type of the struct this is embedded in.
- * \param member the name of the list_struct within the struct.
+ * \param ptr The list head pointer.
+ * \param type The type of containing structure.
+ * \param member The name of the list head member within the structure.
*/
-#define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
+#define list_entry(ptr, type, member) container_of(ptr, type, member)
/**
- * iterate over list of given type
+ * Iterate over a list.
*
- * \param pos the type * to use as a loop counter.
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
+ * \param pos A struct pointer which serves as the iterator.
+ * \param head The head of the list.
+ * \param member The name of the list head member within the structure.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
- * iterate over list of given type safe against removal of list entry
+ * Iterate over list, safe against removal of list entry.
*
- * \param pos the type * to use as a loop counter.
- * \param n another type * to use as temporary storage
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
+ * \param pos The iterator struct pointer.
+ * \param n A second struct pointer which is used as temporary storage.
+ * \param head The head of the list.
+ * \param member The name of the list head member within the structure.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
-/**
- * iterate backwards over list of given type safe against removal of list entry
- * \param pos the type * to use as a loop counter.
- * \param n another type * to use as temporary storage
- * \param head the head for your list.
- * \param member the name of the list_struct within the struct.
- */
-#define list_for_each_entry_safe_reverse(pos, n, head, member) \
- for (pos = list_entry((head)->prev, typeof(*pos), member), \
- n = list_entry(pos->member.prev, typeof(*pos), member); \
- &pos->member != (head); \
- pos = n, n = list_entry(n->member.prev, typeof(*n), member))
/**
- * Get the first element from a list
- * \param ptr the list head to take the element from.
+ * Get the first element of a list.
+ *
+ * \param ptr The list head to take the element from.
* \param type The type of the struct this is embedded in.
* \param member The name of the list_struct within the struct.
*
- * Note that list is expected to be not empty.
+ * Note that the list is expected to be non-empty.
*/
#define list_first_entry(ptr, type, member) \
- list_entry((ptr)->next, type, member)
-
-/**
- * Test whether a list has just one entry.
- *
- * \param head The list to test.
- */
-static inline int list_is_singular(const struct list_head *head)
-{
- return !list_empty(head) && (head->next == head->prev);
-}
-
+ list_entry((ptr)->next, type, member)
the vss status flags, effectively stopping playback.
[/description]
-[subcommand tasks]
- purpose = list active server tasks (deprecated)
- aux_info = NO_PERMISSION_REQUIRED
- [description]
- This used to print the ID, the status and the name of each task,
- mainly for debugging purposes. As of version 0.6.2, the subcommand
- prints nothing. It will be removed in 0.7.0. Don't use.
- [/description]
-
[subcommand term]
purpose = ask the server to terminate
aux_info = VSS_READ | VSS_WRITE
+++ /dev/null
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mm.c Paraslash's mood methods. */
-
-#include <regex.h>
-#include <fnmatch.h>
-#include <osl.h>
-#include <lopsub.h>
-
-#include "para.h"
-#include "error.h"
-#include "string.h"
-#include "afh.h"
-#include "afs.h"
-#include "mm.h"
-
-/** The comparators for numeric mood methods (year, bitrate, ...). */
-#define MOOD_COMPARATORS \
- MC(LESS, <) \
- MC(LESS_OR_EQUAL, <=) \
- MC(EQUAL, =) \
- MC(EQUAL2, ==) \
- MC(NOT_EQUAL, !=) \
- MC(NOT_EQUAL2, <>) \
- MC(GREATER, >) \
- MC(GREATER_OR_EQUAL, >=) \
-
-/** Prefix mood comparator name with "_MC", example: MC_LESS. */
-#define MC(a, b) MC_ ## a,
-/** Each mood comparator is identified by an integer of this type. */
-enum mood_comparator_id {MOOD_COMPARATORS NUM_MOOD_COMPARATORS};
-#undef MC
-/** Stringfied mood comparator, example: "<". */
-#define MC(a, b) # b,
-/** Array of mood comparators represented as C strings ("<", "<=", ...). */
-static const char *mood_comparators[] = {MOOD_COMPARATORS};
-#undef MC
-
-static int parse_mood_comparator(const char *word)
-{
- int i;
-
- for (i = 0; i < NUM_MOOD_COMPARATORS; i++)
- if (!strcmp(word, mood_comparators[i]))
- return i;
- return -E_MOOD_SYNTAX;
-}
-
-struct mm_compare_num_data {
- /** <, <=, =, !=, >=, or >. */
- enum mood_comparator_id id;
- /** The value given at the mood line. */
- int32_t arg;
-};
-
-static int mm_compare_num_score_function(int32_t val,
- const struct mm_compare_num_data *cnd)
-{
- int res;
- int32_t arg = cnd->arg;
-
- switch (cnd->id) {
- case MC_LESS:
- res = val < arg; break;
- case MC_LESS_OR_EQUAL:
- res = val <= arg; break;
- case MC_EQUAL:
- case MC_EQUAL2:
- res = val == arg; break;
- case MC_NOT_EQUAL:
- case MC_NOT_EQUAL2:
- res = val != arg; break;
- case MC_GREATER:
- res = val > arg; break;
- case MC_GREATER_OR_EQUAL:
- res = val >= arg; break;
- default:
- PARA_EMERG_LOG("BUG: invalid mood comparator\n");
- exit(EXIT_FAILURE);
- }
- return res? 100 : -100;
-}
-
-static int mm_compare_num_parser(int argc, char **argv, void **private)
-{
- int ret;
- enum mood_comparator_id id;
- int32_t arg;
- struct mm_compare_num_data *cnd;
- if (argc != 2)
- return -E_MOOD_SYNTAX;
- ret = parse_mood_comparator(argv[1]);
- if (ret < 0)
- return ret;
- id = ret;
- ret = para_atoi32(argv[2], &arg);
- if (ret < 0)
- return ret;
- cnd = para_malloc(sizeof(struct mm_compare_num_data));
- cnd->id = id;
- cnd->arg = arg;
- *private = cnd;
- return 1;
-}
-
-static int mm_regex_parser(int argc, char **argv, void **private)
-{
- regex_t *preg;
- int ret;
-
- if (argc != 1)
- return -E_MOOD_SYNTAX;
- preg = para_malloc(sizeof(*preg));
- ret = para_regcomp(preg, argv[1], REG_EXTENDED | REG_NOSUB);
- if (ret < 0) {
- free(preg);
- return ret;
- }
- *private = preg;
- return 1;
-}
-
-static int mm_regex_score_function(const regex_t *preg, const char *pattern)
-{
- return regexec(preg, pattern, 0, NULL, 0) == 0? 100 : -100;
-}
-
-static void mm_regex_cleanup(void *private)
-{
- regex_t *preg = private;
- regfree(preg);
- free(preg);
-}
-
-static int mm_artist_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.artist);
-}
-
-static int mm_title_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.title);
-}
-
-static int mm_album_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.album);
-}
-
-static int mm_comment_matches_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_regex_score_function(private, afhi->tags.comment);
-}
-
-static int mm_bitrate_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afhi->bitrate, private);
-}
-
-static int mm_frequency_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afhi->frequency, private);
-}
-
-static int mm_channels_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afhi->channels, private);
-}
-
-static int mm_image_id_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afsi->image_id, private);
-}
-
-static int mm_lyrics_id_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afsi->lyrics_id, private);
-}
-
-static int mm_num_played_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *private)
-{
- return mm_compare_num_score_function(afsi->num_played, private);
-}
-
-struct mm_year_data {
- /** Comparator and year given at the mood line. */
- struct mm_compare_num_data *cnd;
- /** Used to detect Y2K issues. */
- int32_t current_year;
-};
-
-static int mm_year_parser(int argc, char **argv, void **private)
-{
- int ret;
- struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd));
- time_t current_time;
- struct tm *gmt;
-
- ret = mm_compare_num_parser(argc, argv, (void **)&mmyd->cnd);
- if (ret < 0)
- goto err;
- current_time = time(NULL);
- gmt = gmtime(¤t_time);
- /* tm_year is the number of years since 1900 */
- mmyd->current_year = gmt->tm_year + 1900;
- *private = mmyd;
- return 1;
-err:
- free(mmyd);
- return ret;
-}
-
-static int mm_year_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- const struct afh_info *afhi,
- const void *private)
-{
- const struct mm_year_data *mmyd = private;
- int32_t tag_year;
- int ret = para_atoi32(afhi->tags.year, &tag_year);
-
- if (ret < 0) /* year tag not present or not a number */
- return -100;
- if (tag_year < 0)
- return -100;
- /* try to work around Y2K issues */
- if (tag_year < 100) {
- tag_year += 1900;
- if (tag_year + 100 <= mmyd->current_year)
- tag_year += 100; /* assume tag_year >= 2000 */
- }
- return mm_compare_num_score_function(tag_year, mmyd->cnd);
-}
-
-static void mm_year_cleanup(void *private)
-{
- struct mm_year_data *mmyd = private;
-
- free(mmyd->cnd);
- free(mmyd);
-}
-
-static int mm_no_attributes_set_parser(int argc, __a_unused char **argv,
- __a_unused void **ignored)
-{
- return argc? -E_MOOD_SYNTAX : 1;
-}
-
-static int mm_no_attributes_set_score_function(__a_unused const char *path,
- const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- __a_unused const void *data)
-{
- if (!afsi->attributes)
- return 100;
- return -100;
-}
-
-static int mm_path_matches_score_function(const char *path,
- __a_unused const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *data)
-{
- if (fnmatch(data, path, 0))
- return -100;
- return 100;
-}
-
-static int mm_path_matches_parser(int argc, char **argv, void **data)
-{
- if (argc != 1)
- return -E_MOOD_SYNTAX;
- *data = para_strdup(argv[1]);
- return 1;
-}
-
-static void mm_path_matches_cleanup(void *data)
-{
- free(data);
-}
-
-static int mm_is_set_parser(int argc, char **argv, void **bitnum)
-{
- int ret;
- unsigned char c, *res;
-
- if (argc != 1)
- return -E_MOOD_SYNTAX;
- ret = get_attribute_bitnum_by_name(argv[1], &c);
- if (ret < 0)
- return ret;
- res = para_malloc(1);
- *res = c;
- *bitnum = res;
- return 1;
-}
-
-static int mm_is_set_score_function(__a_unused const char *path,
- __a_unused const struct afs_info *afsi,
- __a_unused const struct afh_info *afhi,
- const void *data)
-{
- const unsigned char *bn = data;
- if (afsi->attributes & (1ULL << *bn))
- return 100;
- return -100;
-}
-
-#define DEFINE_MOOD_METHOD(_name) \
-.parser = mm_ ## _name ## _parser, \
-.score_function = mm_ ## _name ## _score_function, \
-.name = #_name
-
-#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \
- DEFINE_MOOD_METHOD(_name), \
- .cleanup = mm_ ## _name ## _cleanup
-
-#define DEFINE_REGEX_MOOD_METHOD(_name) \
- .name = #_name, \
- .parser = mm_regex_parser, \
- .score_function = mm_ ## _name ## _score_function, \
- .cleanup = mm_regex_cleanup
-
-#define DEFINE_COMPARE_NUM_MOOD_METHOD(_name) \
- .name = #_name, \
- .parser = mm_compare_num_parser, \
- .score_function = mm_ ## _name ## _score_function
-
-const struct mood_method mood_methods[] = {
- {DEFINE_MOOD_METHOD(no_attributes_set)},
- {DEFINE_MOOD_METHOD(is_set)},
- {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)},
- {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)},
- {DEFINE_REGEX_MOOD_METHOD(artist_matches)},
- {DEFINE_REGEX_MOOD_METHOD(title_matches)},
- {DEFINE_REGEX_MOOD_METHOD(album_matches)},
- {DEFINE_REGEX_MOOD_METHOD(comment_matches)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(bitrate)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(frequency)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(channels)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(num_played)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(image_id)},
- {DEFINE_COMPARE_NUM_MOOD_METHOD(lyrics_id)},
- {.parser = NULL}
-};
+++ /dev/null
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mm.h Symbols and declarations for mood methods. */
-
-/**
- * Assign scores according to a mood_method.
- *
- * Each mood_method has its own mood_score_function. The first three parameters
- * passed to that function are informations about the audio file whose score is
- * to be computed. The data argument depends on the mood method this function
- * is used for. It usually is the argument given at the end of a mood line.
- *
- * Mood score functions must return values between -100 and +100 inclusively.
- * Boolean score functions should always return either -100 or +100.
- *
- * \sa struct \ref mood_method, \ref mood_parser.
- */
-typedef int mood_score_function(const char *path, const struct afs_info *afsi,
- const struct afh_info *afhi, const void *data);
-
-/**
- * Pre-process a mood line.
- *
- * The mood_parser of a mood_method is called once at mood open time for each
- * line of the current mood definition that contains the mood_method's name as
- * a keyword. The line is passed to the mood_parser as the first argument. The
- * mood_parser must determine whether the line is syntactically correct and
- * return a positive value if so and a negative value otherwise.
- *
- * Some mood parsers pre-process the data given in the mood line to compute a
- * structure which depends of the particular mood_method and which is used
- * later in the mood_score_function of the mood_method. The mood_parser may
- * store a pointer to its structure via the void** pointer.
- *
- * \sa \ref mood_cleanup_function, \ref mood_score_function.
- */
-typedef int mood_parser(int, char **, void **);
-
-/**
- * Deallocate resources which were allocated by the mood_parser.
- *
- * Function to free the resources allocated in \ref mood_method::parser. The
- * argument is a pointer to mood method specific data returned by ->parser().
- */
-typedef void mood_cleanup_function(void *);
-
-/**
- * Used for scoring and to determine whether a file is admissible.
- */
-struct mood_method {
- /** The name of the method. */
- const char *name;
- /** Pointer to the mood parser. */
- mood_parser *parser;
- /** Pointer to the score function */
- mood_score_function *score_function;
- /** Optional cleanup function. */
- mood_cleanup_function *cleanup;
-};
-
-/** The array of available mood methods. */
-extern const struct mood_method mood_methods[];
#include "afh.h"
#include "afs.h"
#include "list.h"
-#include "mm.h"
#include "mood.h"
/*
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;
};
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);
}
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);
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)
return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd);
}
-static long compute_score(struct afs_info *afsi, long mood_score)
+static long compute_score(struct afs_info *afsi)
{
- mood_score -= normalized_value(afsi->num_played, statistics.num,
+ long 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,
+ score -= normalized_value(afsi->last_played, statistics.num,
statistics.last_played_sum, statistics.last_played_qd);
- return mood_score / 3;
+ return score / 2;
}
static int add_afs_statistics(const struct osl_row *row)
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);
}
/**
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);
}
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);
}
/**
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;
}
/**
.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);
MP_AFHI(channels)
/** \endcond */
+/**
+ * Return the duration of the audio file from the afh info structure.
+ *
+ * \param ctx See \ref mp_path().
+ *
+ * The duration is computed by multiplying the number of chunks and the
+ * duration of one chunk.
+ *
+ * \return The approximate number of milliseconds.
+ */
+int64_t mp_duration(struct mp_context *ctx)
+{
+ struct timeval tmp;
+ int ret = get_afhi(ctx);
+
+ if (ret < 0)
+ return 0;
+ tv_scale(ctx->afhi.chunks_total, &ctx->afhi.chunk_tv, &tmp);
+ return tv2ms(&tmp);
+}
+
/**
* Define a function which extracts and returns the value of a meta tag.
*
char *mp_path(struct mp_context *ctx);
int64_t mp_year(struct mp_context *ctx);
int64_t mp_num_attributes_set(struct mp_context *ctx);
+int64_t mp_duration(struct mp_context *ctx);
/* Generated with MP_AFSI() */
/** \cond MP_AFSI */
{
struct flowopts *new = para_malloc(sizeof(*new));
- INIT_LIST_HEAD(&new->sockopts);
+ init_list_head(&new->sockopts);
return new;
}
if (sound_device_is_busy())
return 0;
- get_btr_sample_rate(btrn, &rate);
- get_btr_channels(btrn, &ch);
- get_btr_sample_format(btrn, &format);
+ ret = get_btr_sample_rate(btrn, &rate);
+ if (ret < 0)
+ goto out;
+ ret = get_btr_channels(btrn, &ch);
+ if (ret < 0)
+ goto out;
+ ret = get_btr_sample_format(btrn, &format);
+ if (ret < 0)
+ goto out;
ret = oss_init(wn, rate, ch, format);
if (ret < 0)
goto out;
if (btr_get_input_queue_size(pt->btrn) > 0)
return;
}
- ie9_print_status_bar(str, len);
+ i9e_print_status_bar(str, len);
}
/*
assert(info->post_select);
if (!s->task_list.next)
- INIT_LIST_HEAD(&s->task_list);
+ init_list_head(&s->task_list);
t->info = *info;
t->name = para_strdup(info->name);
}
ss->default_port = default_port;
- INIT_LIST_HEAD(&ss->client_list);
+ init_list_head(&ss->client_list);
/* Initialize an access control list */
- INIT_LIST_HEAD(&ss->acl);
+ init_list_head(&ss->acl);
for (i = 0; i < lls_opt_given(acl_opt_result); i++) {
const char *arg = lls_string_val(i, acl_opt_result);
char addr[16];
} \
)
-__must_check __malloc void *para_realloc(void *p, size_t size);
+__must_check void *para_realloc(void *p, size_t size);
__must_check __malloc void *para_malloc(size_t size);
__must_check __malloc void *para_calloc(size_t size);
__must_check __malloc char *para_strdup(const char *s);
const struct lls_opt_result *r_b;
ctx = fn->private_data = para_calloc(sizeof(*ctx));
- INIT_LIST_HEAD(&ctx->buddies);
+ init_list_head(&ctx->buddies);
/* create socket to listen for incoming packets */
ret = makesock(
struct sender_command_data scd;
int i;
- INIT_LIST_HEAD(&targets);
+ init_list_head(&targets);
for (i = 0; i < OPT_GIVEN(UDP_TARGET); i++) {
const char *arg = lls_string_val(i, OPT_RESULT(UDP_TARGET));
if (udp_resolve_target(arg, &scd) < 0)
/* Initialize the list of udp targets. */
static void udp_send_init(void)
{
- INIT_LIST_HEAD(&targets);
+ init_list_head(&targets);
sender_status = SENDER_off;
udp_init_target_list();
if (!OPT_GIVEN(UDP_NO_AUTOSTART))
vsst->afs_socket = afs_socket;
ms2tv(announce_time, &vsst->announce_tv);
PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv));
- INIT_LIST_HEAD(&fec_client_list);
+ init_list_head(&fec_client_list);
FOR_EACH_SENDER(i) {
PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name);
senders[i]->init();
[<a href="para_filter.man.html">para_filter</a>]
[<a href="para_write.man.html">para_write</a>]
[<a href="para_gui.man.html">para_gui</a>]
- [<a href="para_mixer.man.html">para_mixere</a>]
+ [<a href="para_mixer.man.html">para_mixer</a>]
[<a href="para_play.man.html">para_play</a>]
[<a href="para_upgrade_db.man.html">para_upgrade_db</a>]
</p>
checkout of any of the four integration branches maint,
master, next, pu (see the
- <a href="manual.html#Git.branches">Git branches</a>
+ <a href="manual.html#Git-branches">Git branches</a>
section of the manual). All previous releases
correspond to tagged commits and may be checked out
`bitrate` | integer | The average bitrate
`frequency` | integer | The output sample rate
`channels` | integer | The number of channels
+`duration` | integer | The number of milliseconds
`is_set("foo")` | boolean | True if attribute "foo" is set.
[\*] For most audio formats, the year tag is stored as a string. It
const char *writer_name(int wid);
void register_writer_node(struct writer_node *wn, struct btr_node *parent,
struct sched *s);
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
-void get_btr_channels(struct btr_node *btrn, int32_t *result);
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
+int get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
+int get_btr_channels(struct btr_node *btrn, int32_t *result);
+int get_btr_sample_format(struct btr_node *btrn, int32_t *result);
void print_writer_helps(bool detailed);
}
}
-static void get_btr_value(struct btr_node *btrn, const char *cmd,
+static int get_btr_value(struct btr_node *btrn, const char *cmd,
int32_t *result)
{
char *buf = NULL;
int ret = btr_exec_up(btrn, cmd, &buf);
- if (ret < 0) {
- /*
- * This really should not happen. It means one of our parent
- * nodes died unexpectedly. Proceed with fingers crossed.
- */
- PARA_CRIT_LOG("cmd %s: %s\n", cmd, para_strerror(-ret));
- *result = 0;
- return;
- }
+ *result = 0;
+ /*
+ * Errors may happen when the decoder returns EOF before the writer had
+ * a chance to query the buffer tree for the channel count, sample rate
+ * etc.
+ */
+ if (ret < 0)
+ return ret;
ret = para_atoi32(buf, result);
assert(ret >= 0);
free(buf);
+ return ret;
}
/**
* \param btrn Where to start the search.
* \param result Filled in by this function.
*
- * This function is assumed to succeed and terminates on errors.
+ * \return Standard.
*/
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
+int get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
{
- get_btr_value(btrn, "sample_rate", result);
+ return get_btr_value(btrn, "sample_rate", result);
}
/**
*
* \param btrn See \ref get_btr_sample_rate.
* \param result See \ref get_btr_sample_rate.
+ *
+ * \return Standard.
*/
-void get_btr_channels(struct btr_node *btrn, int32_t *result)
+int get_btr_channels(struct btr_node *btrn, int32_t *result)
{
- get_btr_value(btrn, "channels", result);
+ return get_btr_value(btrn, "channels", result);
}
/**
*
* \param btrn See \ref get_btr_sample_rate.
* \param result Contains the sample format as an enum sample_format type.
+ *
+ * \return Standard.
*/
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result)
+int get_btr_sample_format(struct btr_node *btrn, int32_t *result)
{
- get_btr_value(btrn, "sample_format", result);
+ return get_btr_value(btrn, "sample_format", result);
}
bitrate {return BITRATE;}
frequency {return FREQUENCY;}
channels {return CHANNELS;}
+duration {return DURATION;}
true {return TRUE;}
false {return FALSE;}
case CHANNELS:
result->intval= mp_channels(ctx);
return ST_INTVAL;
+ case DURATION:
+ result->intval= mp_duration(ctx);
+ return ST_INTVAL;
/* bools */
case IS_SET:
arg = node->children[0]->sv.strval;
%token <node> BITRATE
%token <node> FREQUENCY
%token <node> CHANNELS
+%token <node> DURATION
%token <node> FALSE TRUE
/* keywords without semantic value */
| BITRATE {$$ = mp_new_ast_leaf_node(BITRATE);}
| FREQUENCY {$$ = mp_new_ast_leaf_node(FREQUENCY);}
| CHANNELS {$$ = mp_new_ast_leaf_node(CHANNELS);}
+ | DURATION {$$ = mp_new_ast_leaf_node(DURATION);}
;
boolexp: IS_SET '(' STRING_LITERAL ')' {$$ = ast_node_new_unary(IS_SET, $3);}