vss: Improve comment about sending empty chunks.
[paraslash.git] / mixer.c
1 /*
2  * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file mixer.c A volume fader and alarm clock for OSS. */
8
9 #include <regex.h>
10 #include <lopsub.h>
11 #include <math.h>
12
13 #include "mixer.lsg.h"
14 #include "para.h"
15 #include "fd.h"
16 #include "string.h"
17 #include "mix.h"
18 #include "error.h"
19 #include "version.h"
20
21 /** Array of error strings. */
22 DEFINE_PARA_ERRLIST;
23
24 /* At least one of the two is defined if this file gets compiled. */
25 static const struct mixer *mixers[] = {
26 #ifdef HAVE_ALSA
27         &alsa_mixer,
28 #endif
29 #ifdef HAVE_OSS
30         &oss_mixer,
31 #endif
32 };
33
34 #define NUM_SUPPORTED_MIXERS (ARRAY_SIZE(mixers))
35 #define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++)
36
37 static struct lls_parse_result *lpr, *sub_lpr;
38
39 #define CMD_PTR(_cmd) (lls_cmd(LSG_MIXER_CMD_ ## _cmd, mixer_suite))
40 #define OPT_RESULT(_cmd, _opt) (lls_opt_result( \
41         LSG_MIXER_ ## _cmd ## _OPT_ ## _opt, (LSG_MIXER_CMD_ ## _cmd == 0)? lpr : sub_lpr))
42 #define OPT_GIVEN(_cmd, _opt) (lls_opt_given(OPT_RESULT(_cmd, _opt)))
43 #define OPT_STRING_VAL(_cmd, _opt) (lls_string_val(0, OPT_RESULT(_cmd, _opt)))
44 #define OPT_UINT32_VAL(_cmd, _opt) (lls_uint32_val(0, OPT_RESULT(_cmd, _opt)))
45
46 typedef int (*mixer_subcommand_handler_t)(const struct mixer *, struct mixer_handle *);
47
48 #define EXPORT_CMD(_cmd) const mixer_subcommand_handler_t \
49         lsg_mixer_com_ ## _cmd ## _user_data = &com_ ## _cmd;
50
51 static int loglevel;
52 static __printf_2_3 void date_log(int ll, const char *fmt, ...)
53 {
54         va_list argp;
55         time_t t1;
56         struct tm *tm;
57
58         if (ll < loglevel)
59                 return;
60         time(&t1);
61         tm = localtime(&t1);
62         fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
63         va_start(argp, fmt);
64         vprintf(fmt, argp);
65         va_end(argp);
66 }
67 __printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
68
69 static int set_channel(const struct mixer *m, struct mixer_handle *h,
70                 const char *channel)
71 {
72
73         PARA_NOTICE_LOG("using %s mixer channel\n", channel?
74                 channel : "default");
75         return m->set_channel(h, channel);
76 }
77
78 static void millisleep(int ms)
79 {
80         struct timespec ts;
81
82         PARA_INFO_LOG("sleeping %dms\n", ms);
83         if (ms < 0)
84                 return;
85         ts.tv_sec = ms / 1000,
86         ts.tv_nsec = (ms % 1000) * 1000 * 1000;
87         nanosleep(&ts, NULL);
88 }
89
90 /*
91  * This implements the inverse function of t -> t^alpha, scaled to the time
92  * interval [0,T] and the range given by old_vol and new_vol. It returns the
93  * amount of milliseconds until the given volume is reached.
94  */
95 static unsigned volume_time(double vol, double old_vol, double new_vol,
96                 double T, double alpha)
97 {
98         double c, d, x;
99
100         if (old_vol < new_vol) {
101                 c = old_vol;
102                 d = new_vol;
103         } else {
104                 c = new_vol;
105                 d = old_vol;
106         }
107
108         x = T * exp(log(((vol - c) / (d - c))) / alpha);
109         assert(x <= T);
110         if (old_vol < new_vol)
111                 return x;
112         else
113                 return T - x;
114 }
115
116 /* Fade to new volume in fade_time seconds. */
117 static int fade(const struct mixer *m, struct mixer_handle *h, uint32_t new_vol,
118                 uint32_t fade_time)
119 {
120         int i, T, old_vol, ret, slept, incr;
121         double ms, alpha;
122         uint32_t fe = OPT_UINT32_VAL(PARA_MIXER, FADE_EXPONENT);
123
124         if (fade_time <= 0 || fe >= 100) {
125                 ret = m->set(h, new_vol);
126                 if (ret < 0)
127                         return ret;
128                 goto sleep;
129         }
130         alpha = (100 - fe) / 100.0;
131         ret = m->get(h);
132         if (ret < 0)
133                 return ret;
134         old_vol = ret;
135         if (old_vol == new_vol)
136                 goto sleep;
137         PARA_NOTICE_LOG("fading %s from %d to %u in %u seconds\n",
138                 OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL), old_vol,
139                 new_vol, fade_time);
140         incr = old_vol < new_vol? 1 : -1;
141         T = fade_time * 1000;
142         i = old_vol;
143         slept = 0;
144         do {
145                 ms = volume_time(i + incr, old_vol, new_vol, T, alpha);
146                 millisleep(ms - slept);
147                 slept = ms;
148                 i += incr;
149                 ret = m->set(h, i);
150                 if (ret < 0)
151                         return ret;
152         } while (i != new_vol);
153         return 1;
154 sleep:
155         sleep(fade_time);
156         return ret;
157 }
158
159 static int com_fade(const struct mixer *m, struct mixer_handle *h)
160 {
161         uint32_t new_vol = OPT_UINT32_VAL(FADE, FADE_VOL);
162         uint32_t fade_time = OPT_UINT32_VAL(FADE, FADE_TIME);
163         return fade(m, h, new_vol, fade_time);
164 }
165 EXPORT_CMD(fade);
166
167 static void client_cmd(const char *cmd)
168 {
169         int ret, status, fds[3] = {0, 0, 0};
170         pid_t pid;
171         char *cmdline = make_message(BINDIR "/para_client %s", cmd);
172
173         PARA_NOTICE_LOG("%s\n", cmdline);
174         ret = para_exec_cmdline_pid(&pid, cmdline, fds);
175         free(cmdline);
176         if (ret < 0) {
177                 PARA_ERROR_LOG("%s\n", para_strerror(-ret));
178                 goto fail;
179         }
180         do
181                 pid = waitpid(pid, &status, 0);
182         while (pid == -1 && errno == EINTR);
183         if (pid < 0) {
184                 PARA_ERROR_LOG("%s\n", strerror(errno));
185                 goto fail;
186         }
187         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
188                 goto fail;
189         return;
190 fail:
191         PARA_EMERG_LOG("command \"%s\" failed\n", cmd);
192         exit(EXIT_FAILURE);
193 }
194
195 static void change_afs_mode(const char *afs_mode)
196 {
197         char *cmd;
198
199         client_cmd("stop");
200         if (!afs_mode)
201                 return;
202         cmd = make_message("select %s", afs_mode);
203         client_cmd(cmd);
204         free(cmd);
205 }
206
207 static int set_initial_volume(const struct mixer *m, struct mixer_handle *h)
208 {
209         int i, ret;
210
211         for (i = 0; i < OPT_GIVEN(SLEEP, IVOL); i++) {
212                 const char *val = lls_string_val(i, OPT_RESULT(SLEEP, IVOL));
213                 char *p, *ch, *arg = para_strdup(val);
214                 int32_t iv;
215                 p = strchr(arg, ':');
216                 if (p) {
217                         *p = '\0';
218                         p++;
219                         ch = arg;
220                 } else {
221                         p = arg;
222                         ch = NULL;
223                 }
224                 ret = para_atoi32(p, &iv);
225                 if (ret < 0) {
226                         free(arg);
227                         return ret;
228                 }
229                 ret = set_channel(m, h, ch);
230                 if (!ch)
231                         ch = "default";
232                 if (ret < 0) {
233                         PARA_WARNING_LOG("ignoring channel %s\n", ch);
234                         ret = 0;
235                 } else {
236                         PARA_INFO_LOG("initial volume %s: %d\n", ch, iv);
237                         ret = m->set(h, iv);
238                 }
239                 free(arg);
240                 if (ret < 0)
241                         return ret;
242         }
243         return 1;
244 }
245
246 static int com_sleep(const struct mixer *m, struct mixer_handle *h)
247 {
248         time_t t1, wake_time_epoch;
249         unsigned int delay;
250         struct tm *tm;
251         int ret;
252         const char *wake_time = OPT_STRING_VAL(SLEEP, WAKE_TIME);
253         const char *fo_mood = OPT_STRING_VAL(SLEEP, FO_MOOD);
254         const char *fi_mood = OPT_STRING_VAL(SLEEP, FI_MOOD);
255         const char *sleep_mood = OPT_STRING_VAL(SLEEP, SLEEP_MOOD);
256         int fit = OPT_UINT32_VAL(SLEEP, FI_TIME);
257         int fot = OPT_UINT32_VAL(SLEEP, FO_TIME);
258         int fiv = OPT_UINT32_VAL(SLEEP, FI_VOL);
259         int fov = OPT_UINT32_VAL(SLEEP, FO_VOL);
260         int32_t hour, min = 0;
261         char *tmp;
262         char *wt = para_strdup(wake_time + (wake_time[0] == '+'));
263
264         /* calculate wake time */
265         time(&t1);
266         tmp = strchr(wt, ':');
267         if (tmp) {
268                 *tmp = '\0';
269                 tmp++;
270                 ret = para_atoi32(tmp, &min);
271                 if (ret < 0) {
272                         free(wt);
273                         return ret;
274                 }
275         }
276         ret = para_atoi32(wt, &hour);
277         free(wt);
278         if (ret < 0)
279                 return ret;
280         if (wake_time[0] == '+') { /* relative */
281                 t1 += hour * 60 * 60 + min * 60;
282                 tm = localtime(&t1);
283         } else {
284                 tm = localtime(&t1);
285                 if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
286                         t1 += 86400; /* wake time is tomorrow */
287                         tm = localtime(&t1);
288                 }
289                 tm->tm_hour = hour;
290                 tm->tm_min = min;
291                 tm->tm_sec = 0;
292         }
293         wake_time_epoch = mktime(tm);
294         PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
295         client_cmd("stop");
296         sleep(1);
297         if (fot) {
298                 ret = set_initial_volume(m, h);
299                 if (ret < 0)
300                         return ret;
301                 change_afs_mode(fo_mood);
302                 client_cmd("play");
303                 ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
304                 if (ret < 0)
305                         return ret;
306                 ret = fade(m, h, fov, fot);
307                 if (ret < 0)
308                         return ret;
309         } else {
310                 ret = m->set(h, fov);
311                 if (ret < 0)
312                         return ret;
313         }
314         if (OPT_GIVEN(SLEEP, SLEEP_MOOD)) {
315                 change_afs_mode(sleep_mood);
316                 client_cmd("play");
317         } else
318                 client_cmd("stop");
319         if (!fit)
320                 return 1;
321         change_afs_mode(fi_mood);
322         for (;;) {
323                 time(&t1);
324                 if (wake_time_epoch <= t1 + fit)
325                         break;
326                 delay = wake_time_epoch - t1 - fit;
327                 PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
328                         delay, delay / 3600,
329                         (delay % 3600) / 60);
330                 sleep(delay);
331         }
332         client_cmd("play");
333         ret = fade(m, h, fiv, fit);
334         PARA_INFO_LOG("fade complete, returning\n");
335         return ret;
336 }
337 EXPORT_CMD(sleep);
338
339 static int com_snooze(const struct mixer *m, struct mixer_handle *h)
340 {
341         int ret, val;
342
343         if (OPT_UINT32_VAL(SNOOZE, SO_TIME) == 0)
344                 return 1;
345         ret = m->get(h);
346         if (ret < 0)
347                 return ret;
348         val = ret;
349         if (val < OPT_UINT32_VAL(SNOOZE, SO_VOL))
350                 ret = m->set(h, OPT_UINT32_VAL(SNOOZE, SO_VOL));
351         else
352                 ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SO_VOL),
353                         OPT_UINT32_VAL(SNOOZE, SO_TIME));
354         if (ret < 0)
355                 return ret;
356         client_cmd("pause");
357         PARA_NOTICE_LOG("%" PRIu32 " seconds snooze time...\n",
358                 OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
359         sleep(OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
360         client_cmd("play");
361         return fade(m, h, OPT_UINT32_VAL(SNOOZE, SI_VOL),
362                 OPT_UINT32_VAL(SNOOZE, SI_TIME));
363 }
364 EXPORT_CMD(snooze);
365
366 static int com_set(const struct mixer *m, struct mixer_handle *h)
367 {
368         return m->set(h, OPT_UINT32_VAL(SET, VAL));
369 }
370 EXPORT_CMD(set);
371
372 static const struct mixer *get_mixer_or_die(void)
373 {
374         int i;
375
376         if (!OPT_GIVEN(PARA_MIXER, MIXER_API))
377                 i = 0; /* default: use first mixer */
378         else
379                 FOR_EACH_MIXER(i)
380                         if (!strcmp(mixers[i]->name,
381                                         OPT_STRING_VAL(PARA_MIXER, MIXER_API)))
382                                 break;
383         if (i < NUM_SUPPORTED_MIXERS) {
384                 PARA_NOTICE_LOG("using %s mixer API\n", mixers[i]->name);
385                 return mixers[i];
386         }
387         printf("available mixer APIs: ");
388         FOR_EACH_MIXER(i)
389                 printf("%s%s%s ", i == 0? "[" : "", mixers[i]->name,
390                         i == 0? "]" : "");
391         printf("\n");
392         exit(EXIT_FAILURE);
393 }
394
395 static void show_subcommands(void)
396 {
397         const struct lls_command *cmd;
398         int i;
399         printf("Subcommands:\n");
400         for (i = 1; (cmd = lls_cmd(i, mixer_suite)); i++) {
401                 const char *name = lls_command_name(cmd);
402                 const char *purpose = lls_purpose(cmd);
403                 printf("%-20s%s\n", name, purpose);
404         }
405 }
406
407
408 static int com_help(__a_unused const struct mixer *m,
409                 __a_unused struct mixer_handle *h)
410 {
411         const struct lls_command *cmd;
412         const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG);
413         char *txt, *errctx;
414         const char *name;
415         int ret;
416
417         ret = lls_check_arg_count(sub_lpr, 0, 1, NULL);
418         if (ret < 0)
419                 return ret;
420         if (lls_num_inputs(sub_lpr) == 0) {
421                 show_subcommands();
422                 return 0;
423         }
424         name = lls_input(0, sub_lpr);
425         ret = lls(lls_lookup_subcmd(name, mixer_suite, &errctx));
426         if (ret < 0) {
427                 if (errctx)
428                         PARA_ERROR_LOG("%s\n", errctx);
429                 free(errctx);
430                 return ret;
431         }
432         cmd = lls_cmd(ret, mixer_suite);
433         if (lls_opt_given(r_l))
434                 txt = lls_long_help(cmd);
435         else
436                 txt = lls_short_help(cmd);
437         printf("%s", txt);
438         free(txt);
439         return 0;
440 }
441 EXPORT_CMD(help);
442
443 static void handle_help_flags(void)
444 {
445         char *help;
446
447         if (OPT_GIVEN(PARA_MIXER, DETAILED_HELP))
448                 help = lls_long_help(CMD_PTR(PARA_MIXER));
449         else if (OPT_GIVEN(PARA_MIXER, HELP))
450                 help = lls_short_help(CMD_PTR(PARA_MIXER));
451         else
452                 return;
453         printf("%s", help);
454         free(help);
455         show_subcommands();
456         exit(EXIT_SUCCESS);
457 }
458
459 static int parse_and_merge_config_file(const struct lls_command *cmd)
460 {
461         int ret;
462         int cf_argc;
463         char **cf_argv;
464         char *cf, *errctx = NULL;
465         struct lls_parse_result **lprp, *cf_lpr, *merged_lpr;
466         void *map;
467         size_t sz;
468         const char *subcmd_name;
469
470         if (cmd == lls_cmd(0, mixer_suite)) {
471                 lprp = &lpr;
472                 subcmd_name = NULL;
473         } else {
474                 lprp = &sub_lpr;
475                 subcmd_name = lls_command_name(cmd);
476         }
477         if (OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
478                 cf = para_strdup(OPT_STRING_VAL(PARA_MIXER, CONFIG_FILE));
479         else {
480                 char *home = para_homedir();
481                 cf = make_message("%s/.paraslash/mixer.conf", home);
482                 free(home);
483         }
484         ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
485         if (ret < 0) {
486                 if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
487                         goto free_cf;
488                 if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
489                                 OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
490                         goto free_cf;
491         } else {
492                 ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
493                 para_munmap(map, sz);
494                 if (ret < 0)
495                         goto free_cf;
496                 cf_argc = ret;
497                 ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
498                 lls_free_argv(cf_argv);
499                 if (ret < 0)
500                         goto free_cf;
501                 ret = lls(lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx));
502                 lls_free_parse_result(cf_lpr, cmd);
503                 if (ret < 0)
504                         goto free_cf;
505                 lls_free_parse_result(*lprp, cmd);
506                 *lprp = merged_lpr;
507                 loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
508         }
509         ret = 1;
510 free_cf:
511         free(cf);
512         if (errctx)
513                 PARA_ERROR_LOG("%s\n", errctx);
514         free(errctx);
515         return ret;
516 }
517
518 /**
519  * The main function of para_mixer.
520  *
521  * The executable is linked with the alsa or the oss mixer API, or both. It has
522  * a custom log function which prefixes log messages with the current date.
523  *
524  * \param argc Argument counter.
525  * \param argv Argument vector.
526  *
527  * \return EXIT_SUCCESS or EXIT_FAILURE.
528  */
529 int main(int argc, char *argv[])
530 {
531         const struct lls_command *cmd = CMD_PTR(PARA_MIXER);
532         int ret;
533         char *errctx;
534         const char *subcmd;
535         const struct mixer *m;
536         struct mixer_handle *h;
537         unsigned n;
538
539         ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
540         if (ret < 0)
541                 goto fail;
542         loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
543         version_handle_flag("mixer", OPT_GIVEN(PARA_MIXER, VERSION));
544         handle_help_flags();
545
546         n = lls_num_inputs(lpr);
547         if (n == 0) {
548                 show_subcommands();
549                 ret = 0;
550                 goto free_lpr;
551         }
552         ret = parse_and_merge_config_file(cmd);
553         if (ret < 0)
554                 goto free_lpr;
555         subcmd = lls_input(0, lpr);
556         ret = lls(lls_lookup_subcmd(subcmd, mixer_suite, &errctx));
557         if (ret < 0)
558                 goto fail;
559         cmd = lls_cmd(ret, mixer_suite);
560         ret = lls(lls_parse(n, argv + argc - n, cmd, &sub_lpr, &errctx));
561         if (ret < 0)
562                 goto free_lpr;
563         ret = parse_and_merge_config_file(cmd);
564         if (ret < 0)
565                 goto free_lpr;
566         m = get_mixer_or_die();
567         ret = m->open(OPT_STRING_VAL(PARA_MIXER, MIXER_DEVICE), &h);
568         if (ret < 0)
569                 goto free_sub_lpr;
570         ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
571         if (ret == -E_BAD_CHANNEL) {
572                 char *channels = m->get_channels(h);
573                 printf("Available channels: %s\n", channels);
574                 free(channels);
575         }
576         if (ret < 0)
577                 goto close_mixer;
578         ret = (*(mixer_subcommand_handler_t *)(lls_user_data(cmd)))(m ,h);
579 close_mixer:
580         m->close(&h);
581 free_sub_lpr:
582         lls_free_parse_result(sub_lpr, cmd);
583 free_lpr:
584         lls_free_parse_result(lpr, CMD_PTR(PARA_MIXER));
585         if (ret >= 0)
586                 return EXIT_SUCCESS;
587 fail:
588         if (errctx)
589                 PARA_ERROR_LOG("%s\n", errctx);
590         free(errctx);
591         PARA_EMERG_LOG("%s\n", para_strerror(-ret));
592         return EXIT_FAILURE;
593 }