summaryrefslogtreecommitdiff
path: root/server/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/mixer.c')
-rw-r--r--server/mixer.c284
1 files changed, 284 insertions, 0 deletions
diff --git a/server/mixer.c b/server/mixer.c
new file mode 100644
index 0000000..54df1b4
--- /dev/null
+++ b/server/mixer.c
@@ -0,0 +1,284 @@
+/*
+ * dolmetschctl
+ *
+ *
+ * Copyright (C) 2015 Christian Pointner <equinox@spreadspace.org>
+ *
+ * This file is part of dolmetschctl.
+ *
+ * dolmetschctl is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * dolmetschctl is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with dolmetschctl. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <error.h>
+#include <poll.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "mixer.h"
+
+static void free_lang_entry(void* ptr)
+{
+ lang_t* l = ptr;
+ assert(l);
+ free(l->name_);
+ slist_clear(&(l->cmds_));
+ free(l);
+}
+
+static void free_task_entry(void* ptr)
+{
+ task_t* t = ptr;
+ assert(t);
+ free(t->buf_);
+ free(t);
+}
+
+static int mixer_read_config_file(mixer_t* x, int dirfd, const char* filename)
+{
+ assert(dirfd > 0);
+
+ int fd = openat(dirfd, filename, O_RDONLY);
+ if(fd < 0) {
+ error(0, errno, "erorr open('%s')", filename);
+ return -1;
+ }
+ FILE* conf = fdopen(fd, "r");
+ if(conf == NULL) {
+ error(0, errno, "erorr fdopen('%s')", filename);
+ return -1;
+ }
+
+ lang_t* lang = malloc(sizeof(lang_t));
+ assert(lang);
+ assert((lang->name_ = strdup(filename)));
+ slist_init(&(lang->cmds_), free);
+ assert(slist_add(&(x->langs_), lang));
+
+ char buf[256];
+ while(fgets(buf, sizeof(buf), conf) != NULL) {
+ midi_cmd_t* cmd = malloc(sizeof(midi_cmd_t));
+ if(!cmd) {
+ error(0, ENOMEM, "can't create midi command stucture");
+ break;
+ }
+ if(sscanf(buf, "%02hhx %02hhx %02hhx", &((*cmd)[0]), &((*cmd)[1]), &((*cmd)[2])) != 3) {
+ continue;
+ }
+ assert(slist_add(&(lang->cmds_), cmd));
+ }
+
+ fclose(conf);
+ return 0;
+}
+
+static int mixer_read_config(mixer_t* x)
+{
+ char path[1024];
+ int ret = snprintf(path, sizeof(path), "%s/%s", ETCDIR, x->name_);
+ assert(ret < sizeof(path));
+
+ DIR* d = opendir(path);
+ if(d == NULL) {
+ error(0, errno, "MIXER: cannot open config directory '%s'", path);
+ return -1;
+ }
+
+ struct dirent* entry;
+ while((entry = readdir(d))) {
+ if(entry->d_type == DT_REG) {
+ if((ret = mixer_read_config_file(x, dirfd(d), entry->d_name)))
+ break;
+ }
+ }
+
+ if(closedir(d) < 0) {
+ error(0, errno, "MIXER: cannot closeconfig directory '%s'", path);
+ return -1;
+ }
+
+ return ret;
+}
+
+int mixer_init(mixer_t* x, const char* name, const char* device)
+{
+ assert(x != NULL);
+
+ x->name_ = name;
+ x->output_ = NULL;
+ slist_init(&(x->langs_), free_lang_entry);
+ slist_init(&(x->tasks_), free_task_entry);
+ int ret = snd_rawmidi_open(NULL, &(x->output_), device, SND_RAWMIDI_NONBLOCK);
+ if(ret < 0) {
+ error(0, 0, "MIXER: cannot open midi port '%s': %s", device, snd_strerror(ret));
+ return ret;
+ }
+
+ return mixer_read_config(x);;
+}
+
+static void mixer_print_midi_cmds(slist_t cmds)
+{
+ if(!slist_length(&(cmds)))
+ printf("<no commands>\n");
+ else {
+ slist_element_t* tmp;
+ for(tmp = cmds.first_; tmp; tmp = tmp->next_) {
+ midi_cmd_t* c = (midi_cmd_t*)(tmp->data_);
+ printf(" 0x%02X 0x%02X 0x%02X\n", (*c)[0], (*c)[1], (*c)[2]);
+ }
+ }
+}
+
+void mixer_print_langs(mixer_t* x)
+{
+ assert(x);
+
+ printf("languages:\n");
+ if(!slist_length(&(x->langs_)))
+ printf(" <no languages>\n");
+ else {
+ slist_element_t* tmp;
+ for(tmp = x->langs_.first_; tmp; tmp = tmp->next_) {
+ lang_t* l = (lang_t*)(tmp->data_);
+ printf(" %s:\n", l->name_);
+ mixer_print_midi_cmds(l->cmds_);
+ }
+ }
+}
+
+static u_int8_t* mixer_create_task_buf(slist_t cmds, int len)
+{
+ u_int8_t* buf = malloc(len);
+ slist_element_t* tmp;
+ int i;
+ for(tmp = cmds.first_, i=0; tmp; tmp = tmp->next_, i+=sizeof(midi_cmd_t)) {
+ midi_cmd_t* c = (midi_cmd_t*)(tmp->data_);
+ memcpy(&(buf[i]), (*c), sizeof(midi_cmd_t));
+ }
+ return buf;
+}
+
+int mixer_switch_lang(mixer_t* x, const char* lang, void (*done_cb)(void*), void* done_data)
+{
+ slist_element_t* tmp;
+ for(tmp = x->langs_.first_; tmp; tmp = tmp->next_) {
+ lang_t* l = (lang_t*)(tmp->data_);
+ if(!strcmp(lang, l->name_)) {
+ int len = slist_length(&(l->cmds_));
+ if(len > 0) {
+ task_t* task = malloc(sizeof(task_t));
+ assert(task);
+ task->len_ = sizeof(midi_cmd_t) * len;
+ assert((task->buf_ = mixer_create_task_buf(l->cmds_, task->len_)));
+ task->write_idx_ = 0;
+ task->done_cb_ = done_cb;
+ task->done_data_ = done_data;
+ assert(slist_add(&(x->tasks_), task));
+ }
+ return 0;
+ }
+ }
+ error(0, 0, "requested language '%s' not found ... ignoring request", lang);
+ return 0;
+}
+
+void mixer_print_tasks(mixer_t* x)
+{
+ assert(x);
+
+ printf("tasks:\n");
+ if(!slist_length(&(x->tasks_)))
+ printf(" <no tasks>\n");
+ else {
+ slist_element_t* tmp;
+ for(tmp = x->tasks_.first_; tmp; tmp = tmp->next_) {
+ task_t* t = (task_t*)(tmp->data_);
+ int i;
+ printf(" buf(%d) = ", t->len_);
+ for(i = 0; i < t->len_; ++i) {
+ printf("0x%02X%s", t->buf_[i], (i && !((i+1) % 3)) ? ", " : " ");
+ }
+ printf("\n write_idx = %d\n done = %016lX ( %016lX )\n\n", t->write_idx_, (int64_t)(t->done_cb_), (int64_t)(t->done_data_));
+ }
+ }
+}
+
+int mixer_get_poll_fd_count(mixer_t* x)
+{
+ assert(x);
+
+ return snd_rawmidi_poll_descriptors_count(x->output_);
+}
+
+int mixer_get_poll_fds(mixer_t* x, struct pollfd *pfds, int npfds)
+{
+ assert(x && pfds && npfds);
+
+ int ret = snd_rawmidi_poll_descriptors(x->output_, pfds, npfds);
+ if(!slist_length(&(x->tasks_))) {
+ int i;
+ for(i = 0; i < ret; ++i)
+ pfds[i].events = 0;
+ }
+ return ret;
+}
+
+int mixer_handle_revents(mixer_t* x, struct pollfd *pfds, int npfds)
+{
+ assert(x && pfds && npfds);
+
+ int err;
+ unsigned short revents;
+ if((err = snd_rawmidi_poll_descriptors_revents(x->output_, pfds, npfds, &revents)) < 0) {
+ error(0, 0, "MIXER: cannot get poll events: %s", snd_strerror(errno));
+ return -1;
+ }
+ if(pfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
+ error(0, 0, "MIXER: got POLLERR, POLLHUP or POLLNVAL");
+ return -1;
+ }
+ if(!(revents & POLLOUT))
+ return 0;
+
+ assert(x->tasks_.first_);
+ task_t* task = (task_t*)(x->tasks_.first_->data_);
+ assert(task);
+ assert(task->buf_);
+
+ int ret = snd_rawmidi_write(x->output_, &(task->buf_[task->write_idx_]), task->len_ - task->write_idx_);
+ if(ret == -EAGAIN)
+ return 0;
+ if(ret < 0) {
+ error(0, 0, "MIXER: cannot write to midi port: %s", snd_strerror(ret));
+ return -1;
+ }
+ task->write_idx_ += ret;
+ if(task->write_idx_ >= task->len_) {
+ if((err = snd_rawmidi_drain(x->output_)) < 0) {
+ error(0, 0, "MIXER: cannot drain output: %s", snd_strerror(err));
+ return -1;
+ }
+ if(task->done_cb_)
+ task->done_cb_(task->done_data_);
+
+ slist_remove(&(x->tasks_), task);
+ }
+
+ return 0;
+}